diff --git a/paper-server/.gitignore b/paper-server/.gitignore new file mode 100644 index 0000000000..3811c0d849 --- /dev/null +++ b/paper-server/.gitignore @@ -0,0 +1,48 @@ +.gradle/ +build/ + +# Eclipse stuff +/.classpath +/.project +/.settings +/.checkstyle +/.factorypath + +# netbeans +/nbproject +nb*.xml + +# we use maven! +/build.xml + +# maven +/target +dependency-reduced-pom.xml + +# vim +.*.sw[a-p] + +# various other potential build files +/build +/bin +/dist +/manifest.mf + +/world +/logs + +# Mac filesystem dust +.DS_Store + +# intellij +*.iml +*.ipr +*.iws +.idea/ + +/src/main/resources/achievement +/src/main/resources/lang + +# vs code +/.vscode +/.factorypath diff --git a/paper-server/LGPL.txt b/paper-server/LGPL.txt new file mode 100644 index 0000000000..65c5ca88a6 --- /dev/null +++ b/paper-server/LGPL.txt @@ -0,0 +1,165 @@ + GNU LESSER 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/paper-server/LICENCE.txt b/paper-server/LICENCE.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/paper-server/LICENCE.txt @@ -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. + + + Copyright (C) + + 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: + + Copyright (C) + 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/paper-server/build.gradle.kts b/paper-server/build.gradle.kts new file mode 100644 index 0000000000..c056b0fa1b --- /dev/null +++ b/paper-server/build.gradle.kts @@ -0,0 +1,249 @@ +import io.papermc.paperweight.util.* +import java.time.Instant + +plugins { + java + `maven-publish` +} + +val log4jPlugins = sourceSets.create("log4jPlugins") +configurations.named(log4jPlugins.compileClasspathConfigurationName) { + extendsFrom(configurations.compileClasspath.get()) +} +val alsoShade: Configuration by configurations.creating + +// Paper start - configure mockito agent that is needed in newer java versions +val mockitoAgent = configurations.register("mockitoAgent") +abstract class MockitoAgentProvider : CommandLineArgumentProvider { + @get:CompileClasspath + abstract val fileCollection: ConfigurableFileCollection + + override fun asArguments(): Iterable { + return listOf("-javaagent:" + fileCollection.files.single().absolutePath) + } +} +// Paper end - configure mockito agent that is needed in newer java versions + +dependencies { + implementation(project(":paper-api")) + implementation("ca.spottedleaf:concurrentutil:0.0.2") // Paper - Add ConcurrentUtil dependency + // Paper start + implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ + implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 + implementation("net.minecrell:terminalconsoleappender:1.3.0") + implementation("net.kyori:adventure-text-serializer-ansi:4.17.0") // Keep in sync with adventureVersion from Paper-API build file + /* + Required to add the missing Log4j2Plugins.dat file from log4j-core + which has been removed by Mojang. Without it, log4j has to classload + all its classes to check if they are plugins. + Scanning takes about 1-2 seconds so adding this speeds up the server start. + */ + implementation("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - implementation + log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins + runtimeOnly(log4jPlugins.output) + alsoShade(log4jPlugins.output) + implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol + // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") // Paper - remove exclusion + implementation("org.ow2.asm:asm-commons:9.7.1") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files + implementation("commons-lang:commons-lang:2.6") + runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0") + runtimeOnly("com.mysql:mysql-connector-j:9.1.0") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper + + runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") + runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + + testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") + testImplementation("org.hamcrest:hamcrest:2.2") + testImplementation("org.mockito:mockito-core:5.14.1") + mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions + testImplementation("org.ow2.asm:asm-tree:9.7.1") + testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest + implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling + implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins + // Paper start - Remap reflection + val reflectionRewriterVersion = "0.0.3" + implementation("io.papermc:reflection-rewriter:$reflectionRewriterVersion") + implementation("io.papermc:reflection-rewriter-runtime:$reflectionRewriterVersion") + implementation("io.papermc:reflection-rewriter-proxy-generator:$reflectionRewriterVersion") + // Paper end - Remap reflection + // Paper start - spark + implementation("me.lucko:spark-api:0.1-20240720.200737-2") + implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") + // Paper end - spark +} + +paperweight { + craftBukkitPackageVersion.set("v1_21_R3") // also needs to be updated in MappingEnvironment +} + +tasks.jar { + archiveClassifier.set("dev") + + manifest { + val git = Git(rootProject.layout.projectDirectory.path) + val mcVersion = rootProject.providers.gradleProperty("mcVersion").get() + val build = System.getenv("BUILD_NUMBER") ?: null + val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim() + val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash" + val date = git("show", "-s", "--format=%ci", gitHash).getText().trim() // Paper + val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", + "Implementation-Title" to "Paper", + "Implementation-Version" to implementationVersion, + "Implementation-Vendor" to date, // Paper + "Specification-Title" to "Paper", + "Specification-Version" to project.version, + "Specification-Vendor" to "Paper Team", + "Brand-Id" to "papermc:paper", + "Brand-Name" to "Paper", + "Build-Number" to (build ?: ""), + "Build-Time" to Instant.now().toString(), + "Git-Branch" to gitBranch, // Paper + "Git-Commit" to gitHash, // Paper + "CraftBukkit-Package-Version" to paperweight.craftBukkitPackageVersion.get(), // Paper + ) + for (tld in setOf("net", "com", "org")) { + attributes("$tld/bukkit", "Sealed" to true) + } + } +} + +// Paper start - compile tests with -parameters for better junit parameterized test names +tasks.compileTestJava { + options.compilerArgs.add("-parameters") +} +// Paper end + +publishing { + publications.create("maven") { + } +} + +// Paper start +val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.ScanJarForBadCalls::class) { + badAnnotations.add("Lio/papermc/paper/annotation/DoNotUse;") + jarToScan.set(tasks.serverJar.flatMap { it.archiveFile }) + classpath.from(configurations.compileClasspath) +} +tasks.check { + dependsOn(scanJar) +} +// Paper end +// Paper start - use TCA for console improvements +tasks.serverJar { + from(alsoShade.elements.map { + it.map { f -> + if (f.asFile.isFile) { + zipTree(f.asFile) + } else { + f.asFile + } + } + }) +} +// Paper end - use TCA for console improvements + +tasks.test { + include("**/**TestSuite.class") + workingDir = temporaryDir + useJUnitPlatform { + forkEvery = 1 + excludeTags("Slow") + } + // Paper start - configure mockito agent that is needed in newer java versions + val provider = objects.newInstance() + provider.fileCollection.from(mockitoAgent) + jvmArgumentProviders.add(provider) + // Paper end - configure mockito agent that is needed in newer java versions +} + +fun TaskContainer.registerRunTask( + name: String, + block: JavaExec.() -> Unit +): TaskProvider = register(name) { + group = "paper" + mainClass.set("org.bukkit.craftbukkit.Main") + standardInput = System.`in` + workingDir = rootProject.layout.projectDirectory + .dir(providers.gradleProperty("paper.runWorkDir").getOrElse("run")) + .asFile + javaLauncher.set(project.javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(21)) + vendor.set(JvmVendorSpec.JETBRAINS) + }) + jvmArgs("-XX:+AllowEnhancedClassRedefinition", "-XX:+AllowRedefinitionToAddDeleteMethods") + + if (rootProject.childProjects["test-plugin"] != null) { + val testPluginJar = rootProject.project(":test-plugin").tasks.jar.flatMap { it.archiveFile } + inputs.file(testPluginJar) + args("-add-plugin=${testPluginJar.get().asFile.absolutePath}") + } + + args("--nogui") + systemProperty("net.kyori.adventure.text.warnWhenLegacyFormattingDetected", true) + if (providers.gradleProperty("paper.runDisableWatchdog").getOrElse("false") == "true") { + systemProperty("disable.watchdog", true) + } + systemProperty("io.papermc.paper.suppress.sout.nags", true) + + val memoryGb = providers.gradleProperty("paper.runMemoryGb").getOrElse("2") + minHeapSize = "${memoryGb}G" + maxHeapSize = "${memoryGb}G" + + doFirst { + workingDir.mkdirs() + } + + block(this) +} + +val runtimeClasspathWithoutVanillaServer = configurations.runtimeClasspath.flatMap { it.elements } + .zip(configurations.vanillaServer.map { it.singleFile.absolutePath }) { runtime, vanilla -> + runtime.filterNot { it.asFile.absolutePath == vanilla } + } + +tasks.registerRunTask("runServer") { + description = "Spin up a test server from the Mojang mapped server jar" + classpath(tasks.includeMappings.flatMap { it.outputJar }) + classpath(runtimeClasspathWithoutVanillaServer) +} + +tasks.registerRunTask("runReobfServer") { + description = "Spin up a test server from the reobfJar output jar" + classpath(tasks.reobfJar.flatMap { it.outputJar }) + classpath(runtimeClasspathWithoutVanillaServer) +} + +tasks.registerRunTask("runDevServer") { + description = "Spin up a test server without assembling a jar" + classpath(sourceSets.main.map { it.runtimeClasspath }) + jvmArgs("-DPaper.pushPaperAssetsRoot=true") +} + +tasks.registerRunTask("runBundler") { + description = "Spin up a test server from the Mojang mapped bundler jar" + classpath(rootProject.tasks.named("createMojmapBundlerJar").flatMap { it.outputZip }) + mainClass.set(null as String?) +} +tasks.registerRunTask("runReobfBundler") { + description = "Spin up a test server from the reobf bundler jar" + classpath(rootProject.tasks.named("createReobfBundlerJar").flatMap { it.outputZip }) + mainClass.set(null as String?) +} +tasks.registerRunTask("runPaperclip") { + description = "Spin up a test server from the Mojang mapped Paperclip jar" + classpath(rootProject.tasks.named("createMojmapPaperclipJar").flatMap { it.outputZip }) + mainClass.set(null as String?) +} +tasks.registerRunTask("runReobfPaperclip") { + description = "Spin up a test server from the reobf Paperclip jar" + classpath(rootProject.tasks.named("createReobfPaperclipJar").flatMap { it.outputZip }) + mainClass.set(null as String?) +} diff --git a/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch b/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch new file mode 100644 index 0000000000..572f83673b --- /dev/null +++ b/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch @@ -0,0 +1,18 @@ +--- a/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json ++++ b/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json +@@ -70,15 +70,6 @@ + "add": false, + "count": 1.0, + "function": "minecraft:set_count" +- }, +- { +- "add": false, +- "damage": { +- "type": "minecraft:uniform", +- "max": 0.8, +- "min": 0.15 +- }, +- "function": "minecraft:set_damage" + } + ], + "name": "minecraft:compass" diff --git a/paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch b/paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch new file mode 100644 index 0000000000..30f05bd5aa --- /dev/null +++ b/paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch @@ -0,0 +1,23 @@ +--- a/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java ++++ b/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +@@ -44,6 +44,7 @@ + .collect(Collectors.toSet()); + + final int page = 0; ++ boolean hasRequested = false; // Paper - Don't sleep after profile lookups if not needed + + for (final List request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) { + final List normalizedRequest = request.stream().map(YggdrasilGameProfileRepository::normalizeName).toList(); +@@ -75,6 +76,12 @@ + LOGGER.debug("Couldn't find profile {}", name); + callback.onProfileLookupFailed(name, new ProfileNotFoundException("Server did not find the requested profile")); + } ++ // Paper start - Don't sleep after profile lookups if not needed ++ if (!hasRequested) { ++ hasRequested = true; ++ continue; ++ } ++ // Paper end - Don't sleep after profile lookups if not needed + + try { + Thread.sleep(DELAY_BETWEEN_PAGES); diff --git a/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch new file mode 100644 index 0000000000..ba95e196e5 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch @@ -0,0 +1,75 @@ +--- a/com/mojang/brigadier/CommandDispatcher.java ++++ b/com/mojang/brigadier/CommandDispatcher.java +@@ -3,6 +3,7 @@ + + package com.mojang.brigadier; + ++// CHECKSTYLE:OFF + import com.mojang.brigadier.builder.LiteralArgumentBuilder; + import com.mojang.brigadier.context.CommandContext; + import com.mojang.brigadier.context.CommandContextBuilder; +@@ -297,15 +298,21 @@ + List> potentials = null; + final int cursor = originalReader.getCursor(); + +- for (final CommandNode child : node.getRelevantNodes(originalReader)) { ++ for (final CommandNode child : node.getRelevantNodes(originalReader, source)) { // Paper - prioritize mc commands in function parsing + if (!child.canUse(source)) { + continue; + } + final CommandContextBuilder context = contextSoFar.copy(); + final StringReader reader = new StringReader(originalReader); ++ boolean stop = false; // Paper - Handle non-recoverable exceptions + try { + try { + child.parse(reader, context); ++ // Paper start - Handle non-recoverable exceptions ++ } catch (final io.papermc.paper.brigadier.TagParseCommandSyntaxException e) { ++ stop = true; ++ throw e; ++ // Paper end - Handle non-recoverable exceptions + } catch (final RuntimeException ex) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); + } +@@ -320,6 +327,7 @@ + } + errors.put(child, ex); + reader.setCursor(cursor); ++ if (stop) return new ParseResults<>(contextSoFar, originalReader, errors); // Paper - Handle non-recoverable exceptions + continue; + } + +@@ -451,7 +459,7 @@ + } + + private String getSmartUsage(final CommandNode node, final S source, final boolean optional, final boolean deep) { +- if (!node.canUse(source)) { ++ if (source != null && !node.canUse(source)) { // Paper + return null; + } + +@@ -465,7 +473,7 @@ + final String redirect = node.getRedirect() == this.root ? "..." : "-> " + node.getRedirect().getUsageText(); + return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect; + } else { +- final Collection> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList()); ++ final Collection> children = node.getChildren().stream().filter(c -> source == null || c.canUse(source)).collect(Collectors.toList()); // Paper + if (children.size() == 1) { + final String usage = this.getSmartUsage(children.iterator().next(), source, childOptional, childOptional); + if (usage != null) { +@@ -537,10 +545,14 @@ + int i = 0; + for (final CommandNode node : parent.getChildren()) { + CompletableFuture future = Suggestions.empty(); ++ // Paper start - Don't suggest if the requirement isn't met ++ if (parent != this.root || node.canUse(context.getSource())) { + try { +- future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); ++ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit + } catch (final CommandSyntaxException ignored) { + } ++ } ++ // Paper end - Don't suggest if the requirement isn't met + futures[i++] = future; + } + diff --git a/paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch b/paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch new file mode 100644 index 0000000000..16daaa3a3c --- /dev/null +++ b/paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch @@ -0,0 +1,21 @@ +--- a/com/mojang/brigadier/builder/ArgumentBuilder.java ++++ b/com/mojang/brigadier/builder/ArgumentBuilder.java +@@ -14,9 +14,17 @@ + import java.util.function.Predicate; + + public abstract class ArgumentBuilder> { ++ // Paper start - Vanilla command permission fixes ++ private static final Predicate DEFAULT_REQUIREMENT = s -> true; ++ ++ @SuppressWarnings("unchecked") ++ public static Predicate defaultRequirement() { ++ return (Predicate) DEFAULT_REQUIREMENT; ++ } ++ // Paper end - Vanilla command permission fixes + private final RootCommandNode arguments = new RootCommandNode<>(); + private Command command; +- private Predicate requirement = s -> true; ++ private Predicate requirement = defaultRequirement(); // Paper - Vanilla command permission fixes + private CommandNode target; + private RedirectModifier modifier = null; + private boolean forks; diff --git a/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch b/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch new file mode 100644 index 0000000000..b3bbcc46e5 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch @@ -0,0 +1,23 @@ +--- a/com/mojang/brigadier/exceptions/CommandSyntaxException.java ++++ b/com/mojang/brigadier/exceptions/CommandSyntaxException.java +@@ -5,7 +5,7 @@ + + import com.mojang.brigadier.Message; + +-public class CommandSyntaxException extends Exception { ++public class CommandSyntaxException extends Exception implements net.kyori.adventure.util.ComponentMessageThrowable { // Paper - Brigadier API + public static final int CONTEXT_AMOUNT = 10; + public static boolean ENABLE_COMMAND_STACK_TRACES = true; + public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions(); +@@ -73,4 +73,11 @@ + public int getCursor() { + return cursor; + } ++ ++ // Paper start - Brigadier API ++ @Override ++ public @org.jetbrains.annotations.Nullable net.kyori.adventure.text.Component componentMessage() { ++ return io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(this.message); ++ } ++ // Paper end - Brigadier API + } diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch new file mode 100644 index 0000000000..179686de6f --- /dev/null +++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch @@ -0,0 +1,102 @@ +--- a/com/mojang/brigadier/tree/CommandNode.java ++++ b/com/mojang/brigadier/tree/CommandNode.java +@@ -3,6 +3,7 @@ + + package com.mojang.brigadier.tree; + ++// CHECKSTYLE:OFF + import com.mojang.brigadier.AmbiguityConsumer; + import com.mojang.brigadier.Command; + import com.mojang.brigadier.RedirectModifier; +@@ -22,6 +23,7 @@ + import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.function.Predicate; ++import net.minecraft.commands.CommandSourceStack; + + public abstract class CommandNode implements Comparable> { + private final Map> children = new LinkedHashMap<>(); +@@ -32,6 +34,16 @@ + private final RedirectModifier modifier; + private final boolean forks; + private Command command; ++ public CommandNode clientNode; // Paper - Brigadier API ++ public CommandNode unwrappedCached = null; // Paper - Brigadier Command API ++ public CommandNode wrappedCached = null; // Paper - Brigadier Command API ++ // CraftBukkit start ++ public void removeCommand(String name) { ++ this.children.remove(name); ++ this.literals.remove(name); ++ this.arguments.remove(name); ++ } ++ // CraftBukkit end + + protected CommandNode(final Command command, final Predicate requirement, final CommandNode redirect, final RedirectModifier modifier, final boolean forks) { + this.command = command; +@@ -61,7 +73,17 @@ + return this.modifier; + } + +- public boolean canUse(final S source) { ++ // CraftBukkit start ++ public synchronized boolean canUse(final S source) { ++ if (source instanceof CommandSourceStack) { ++ try { ++ ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper - Thread Safe Vanilla Command permission checking ++ return this.requirement.test(source); ++ } finally { ++ ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking ++ } ++ } ++ // CraftBukkit end + return this.requirement.test(source); + } + +@@ -151,6 +173,12 @@ + protected abstract String getSortedKey(); + + public Collection> getRelevantNodes(final StringReader input) { ++ // Paper start - prioritize mc commands in function parsing ++ return this.getRelevantNodes(input, null); ++ } ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public Collection> getRelevantNodes(final StringReader input, final Object source) { ++ // Paper end - prioritize mc commands in function parsing + if (this.literals.size() > 0) { + final int cursor = input.getCursor(); + while (input.canRead() && input.peek() != ' ') { +@@ -158,7 +186,21 @@ + } + final String text = input.getString().substring(cursor, input.getCursor()); + input.setCursor(cursor); +- final LiteralCommandNode literal = this.literals.get(text); ++ // Paper start - prioritize mc commands in function parsing ++ LiteralCommandNode literal = null; ++ if (source instanceof CommandSourceStack css && css.source == net.minecraft.commands.CommandSource.NULL) { ++ if (!text.contains(":")) { ++ literal = this.literals.get("minecraft:" + text); ++ } ++ } else if (source instanceof CommandSourceStack css && css.source instanceof net.minecraft.world.level.BaseCommandBlock) { ++ if (css.getServer().server.getCommandBlockOverride(text) && !text.contains(":")) { ++ literal = this.literals.get("minecraft:" + text); ++ } ++ } ++ if (literal == null) { ++ literal = this.literals.get(text); ++ } ++ // Paper end - prioritize mc commands in function parsing + if (literal != null) { + return Collections.singleton(literal); + } else { +@@ -183,4 +225,11 @@ + } + + public abstract Collection getExamples(); ++ // Paper start - Brigadier Command API ++ public void clearAll() { ++ this.children.clear(); ++ this.literals.clear(); ++ this.arguments.clear(); ++ } ++ // Paper end - Brigadier Command API + } diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch new file mode 100644 index 0000000000..15d0b7b976 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch @@ -0,0 +1,57 @@ +--- a/com/mojang/brigadier/tree/LiteralCommandNode.java ++++ b/com/mojang/brigadier/tree/LiteralCommandNode.java +@@ -23,11 +23,19 @@ + public class LiteralCommandNode extends CommandNode { + private final String literal; + private final String literalLowerCase; ++ private final String nonPrefixed; // Paper - prioritize mc commands in function parsing + + public LiteralCommandNode(final String literal, final Command command, final Predicate requirement, final CommandNode redirect, final RedirectModifier modifier, final boolean forks) { + super(command, requirement, redirect, modifier, forks); + this.literal = literal; + this.literalLowerCase = literal.toLowerCase(Locale.ROOT); ++ // Paper start - prioritize mc commands in function parsing ++ if (literal.startsWith("minecraft:")) { ++ this.nonPrefixed = literal.substring("minecraft:".length()); ++ } else { ++ this.nonPrefixed = null; ++ } ++ // Paper end - prioritize mc commands in function parsing + } + + public String getLiteral() { +@@ -42,7 +50,12 @@ + @Override + public void parse(final StringReader reader, final CommandContextBuilder contextBuilder) throws CommandSyntaxException { + final int start = reader.getCursor(); +- final int end = parse(reader); ++ // Paper start - prioritize mc commands in function parsing ++ int end = parse(reader, false); ++ if (end == -1 && this.nonPrefixed != null) { ++ end = parse(reader, true); ++ } ++ // Paper end - prioritize mc commands in function parsing + if (end > -1) { + contextBuilder.withNode(this, StringRange.between(start, end)); + return; +@@ -51,7 +64,10 @@ + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literal); + } + +- private int parse(final StringReader reader) { ++ // Paper start - prioritize mc commands in function parsing ++ private int parse(final StringReader reader, final boolean secondPass) { ++ String literal = secondPass ? this.nonPrefixed : this.literal; ++ // Paper end - prioritize mc commands in function parsing + final int start = reader.getCursor(); + if (reader.canRead(literal.length())) { + final int end = start + literal.length(); +@@ -78,7 +94,7 @@ + + @Override + public boolean isValidInput(final String input) { +- return parse(new StringReader(input)) > -1; ++ return parse(new StringReader(input), false) > -1; // Paper - prioritize mc commands in function parsing + } + + @Override diff --git a/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch new file mode 100644 index 0000000000..6d1be183b0 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch @@ -0,0 +1,21 @@ +--- a/com/mojang/datafixers/DataFixerBuilder.java ++++ b/com/mojang/datafixers/DataFixerBuilder.java +@@ -29,8 +29,10 @@ + private final Int2ObjectSortedMap schemas = new Int2ObjectAVLTreeMap<>(); + private final List globalList = new ArrayList<>(); + private final IntSortedSet fixerVersions = new IntAVLTreeSet(); ++ private final int minDataFixPrecacheVersion; // Paper - Perf: Cache DataFixerUpper Rewrite Rules on demand + + public DataFixerBuilder(final int dataVersion) { ++ minDataFixPrecacheVersion = Integer.getInteger("Paper.minPrecachedDatafixVersion", dataVersion+1) * 10; // Paper - Perf: default to precache nothing - mojang stores versions * 10 to allow for 'sub versions' + this.dataVersion = dataVersion; + } + +@@ -88,6 +90,7 @@ + final IntIterator iterator = fixerUpper.fixerVersions().iterator(); + while (iterator.hasNext()) { + final int versionKey = iterator.nextInt(); ++ if (versionKey < minDataFixPrecacheVersion) continue; // Paper - Perf: Cache DataFixerUpper Rewrite Rules on demand + final Schema schema = schemas.get(versionKey); + for (final String typeName : schema.types()) { + if (!requiredTypeNames.contains(typeName)) { diff --git a/paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch b/paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch new file mode 100644 index 0000000000..810603f8a3 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch @@ -0,0 +1,38 @@ +--- a/com/mojang/datafixers/util/Either.java ++++ b/com/mojang/datafixers/util/Either.java +@@ -22,7 +22,7 @@ + } + + private static final class Left extends Either { +- private final L value; ++ private final L value; private Optional valueOptional; // Paper - Perf: Reduce Either Optional allocation + + public Left(final L value) { + this.value = value; +@@ -51,7 +51,7 @@ + + @Override + public Optional left() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - Perf: Reduce Either Optional allocation + } + + @Override +@@ -83,7 +83,7 @@ + } + + private static final class Right extends Either { +- private final R value; ++ private final R value; private Optional valueOptional; // Paper - Perf: Reduce Either Optional allocation + + public Right(final R value) { + this.value = value; +@@ -117,7 +117,7 @@ + + @Override + public Optional right() { +- return Optional.of(value); ++ return this.valueOptional == null ? this.valueOptional = Optional.of(this.value) : this.valueOptional; // Paper - Perf: Reduce Either Optional allocation + } + + @Override diff --git a/paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch b/paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch new file mode 100644 index 0000000000..1cabed3cc3 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch @@ -0,0 +1,12 @@ +--- a/com/mojang/logging/LogUtils.java ++++ b/com/mojang/logging/LogUtils.java +@@ -61,4 +61,9 @@ + public static Logger getLogger() { + return LoggerFactory.getLogger(STACK_WALKER.getCallerClass()); + } ++ // Paper start ++ public static Logger getClassLogger() { ++ return LoggerFactory.getLogger(STACK_WALKER.getCallerClass().getSimpleName()); ++ } ++ // Paper end + } diff --git a/paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch b/paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch new file mode 100644 index 0000000000..64c7d5d058 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch @@ -0,0 +1,30 @@ +--- a/com/mojang/math/OctahedralGroup.java ++++ b/com/mojang/math/OctahedralGroup.java +@@ -110,6 +110,7 @@ + this.permutation = axisTransformation; + this.transformation = new Matrix3f().scaling(flipX ? -1.0F : 1.0F, flipY ? -1.0F : 1.0F, flipZ ? -1.0F : 1.0F); + this.transformation.mul(axisTransformation.transformation()); ++ this.initializeRotationDirections(); // Paper - Avoid Lazy Initialization for Enum Fields + } + + private BooleanList packInversions() { +@@ -138,7 +139,7 @@ + return this.name; + } + +- public Direction rotate(Direction direction) { ++ public void initializeRotationDirections() { // Paper - Avoid Lazy Initialization for Enum Fields + if (this.rotatedDirections == null) { + this.rotatedDirections = Maps.newEnumMap(Direction.class); + Direction.Axis[] axiss = Direction.Axis.values(); +@@ -153,6 +154,10 @@ + } + } + ++ // Paper start - Avoid Lazy Initialization for Enum Fields ++ } ++ public Direction rotate(Direction direction) { ++ // Paper end - Avoid Lazy Initialization for Enum Fields + return this.rotatedDirections.get(direction); + } + diff --git a/paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch b/paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch new file mode 100644 index 0000000000..cdd24c2f21 --- /dev/null +++ b/paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch @@ -0,0 +1,19 @@ +--- a/com/mojang/serialization/Dynamic.java ++++ b/com/mojang/serialization/Dynamic.java +@@ -19,6 +19,7 @@ + + @SuppressWarnings("unused") + public class Dynamic extends DynamicLike { ++ private static final boolean DEBUG_MISSING_KEYS = Boolean.getBoolean("Paper.debugDynamicMissingKeys"); // Paper - Perf: Skip toString on values like NBT + private final T value; + + public Dynamic(final DynamicOps ops) { +@@ -120,7 +121,7 @@ + return new OptionalDynamic<>(ops, ops.getMap(value).flatMap(m -> { + final T value = m.get(key); + if (value == null) { +- return DataResult.error(() -> "key missing: " + key + " in " + this.value); ++ return DataResult.error(() -> DEBUG_MISSING_KEYS ? "key missing: " + key + " in " + this.value : "key missing: " + key); // Paper - Perf: Skip toString on values like NBT + } + return DataResult.success(new Dynamic<>(ops, value)); + })); diff --git a/paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch b/paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch new file mode 100644 index 0000000000..eb700c93b5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/ChatFormatting.java ++++ b/net/minecraft/ChatFormatting.java +@@ -112,6 +112,18 @@ + return name == null ? null : FORMATTING_BY_NAME.get(cleanName(name)); + } + ++ // Paper start - add method to get by hex value ++ @Nullable public static ChatFormatting getByHexValue(int i) { ++ for (ChatFormatting value : values()) { ++ if (value.getColor() != null && value.getColor() == i) { ++ return value; ++ } ++ } ++ ++ return null; ++ } ++ // Paper end - add method to get by hex value ++ + @Nullable + public static ChatFormatting getById(int colorIndex) { + if (colorIndex < 0) { diff --git a/paper-server/patches/sources/net/minecraft/CrashReport.java.patch b/paper-server/patches/sources/net/minecraft/CrashReport.java.patch new file mode 100644 index 0000000000..0b8863cd41 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/CrashReport.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/CrashReport.java ++++ b/net/minecraft/CrashReport.java +@@ -34,8 +34,10 @@ + private final SystemReport systemReport = new SystemReport(); + + public CrashReport(String message, Throwable cause) { ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper + this.title = message; + this.exception = cause; ++ this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit + } + + public String getTitle() { +@@ -251,7 +253,7 @@ + } + + public static void preload() { +- MemoryReserve.allocate(); ++ // MemoryReserve.allocate(); // Paper - Disable memory reserve allocating + (new CrashReport("Don't panic!", new Throwable())).getFriendlyReport(ReportType.CRASH); + } + } diff --git a/paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch b/paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch new file mode 100644 index 0000000000..23f5297d2e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/CrashReportCategory.java ++++ b/net/minecraft/CrashReportCategory.java +@@ -110,6 +110,7 @@ + } else { + this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount]; + System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length); ++ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper + return this.stackTrace.length; + } + } diff --git a/paper-server/patches/sources/net/minecraft/Util.java.patch b/paper-server/patches/sources/net/minecraft/Util.java.patch new file mode 100644 index 0000000000..378b82b378 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/Util.java.patch @@ -0,0 +1,143 @@ +--- a/net/minecraft/Util.java ++++ b/net/minecraft/Util.java +@@ -92,9 +92,26 @@ + private static final int DEFAULT_MAX_THREADS = 255; + private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10; + private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; +- private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main"); ++ private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority + private static final TracingExecutor IO_POOL = makeIoExecutor("IO-Worker-", false); ++ public static final TracingExecutor DIMENSION_DATA_IO_POOL = makeExtraIoExecutor("Dimension-Data-IO-Worker-"); // Paper - Separate dimension data IO pool + private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true); ++ // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread ++ public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { ++ ++ private final AtomicInteger count = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(Runnable run) { ++ Thread ret = new Thread(run); ++ ret.setName("Profile Lookup Executor #" + this.count.getAndIncrement()); ++ ret.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ }); ++ return ret; ++ } ++ }); ++ // Paper end - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT); + public static final int LINEAR_LOOKUP_THRESHOLD = 8; + private static final Set ALLOWED_UNTRUSTED_LINK_PROTOCOLS = Set.of("http", "https"); +@@ -136,7 +153,7 @@ + } + + public static long getNanos() { +- return timeSource.getAsLong(); ++ return System.nanoTime(); // Paper + } + + public static long getEpochMillis() { +@@ -147,15 +164,16 @@ + return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now()); + } + +- private static TracingExecutor makeExecutor(String name) { ++ private static TracingExecutor makeExecutor(String name, final int priorityModifier) { // Paper - Perf: add priority + int i = maxAllowedExecutorThreads(); +- ExecutorService executorService; ++ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs ++ final ExecutorService executorService; + if (i <= 0) { + executorService = MoreExecutors.newDirectExecutorService(); + } else { +- AtomicInteger atomicInteger = new AtomicInteger(1); +- executorService = new ForkJoinPool(i, pool -> { +- final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement(); ++ executorService = Executors.newFixedThreadPool(i, target -> new io.papermc.paper.util.ServerWorkerThread(target, name, priorityModifier)); ++ } ++ /* final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement(); + ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) { + @Override + protected void onStart() { +@@ -177,13 +195,26 @@ + forkJoinWorkerThread.setName(string2); + return forkJoinWorkerThread; + }, Util::onThreadException, true); +- } ++ }*/ + + return new TracingExecutor(executorService); + } + + public static int maxAllowedExecutorThreads() { +- return Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads()); ++ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs ++ final int cpus = Runtime.getRuntime().availableProcessors() / 2; ++ int maxExecutorThreads; ++ if (cpus <= 4) { ++ maxExecutorThreads = cpus <= 2 ? 1 : 2; ++ } else if (cpus <= 8) { ++ // [5, 8] ++ maxExecutorThreads = Math.max(3, cpus - 2); ++ } else { ++ maxExecutorThreads = cpus * 2 / 3; ++ } ++ maxExecutorThreads = Math.min(8, maxExecutorThreads); ++ return Integer.getInteger("Paper.WorkerThreadCount", maxExecutorThreads); ++ // Paper end - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs + } + + private static int getMaxThreads() { +@@ -229,10 +260,25 @@ + TracyClient.setThreadName(string2, namePrefix.hashCode()); + thread.setName(string2); + thread.setDaemon(daemon); ++ thread.setUncaughtExceptionHandler(Util::onThreadException); ++ return thread; ++ })); ++ } ++ ++ // Paper start - Separate dimension data IO pool ++ private static TracingExecutor makeExtraIoExecutor(String namePrefix) { ++ AtomicInteger atomicInteger = new AtomicInteger(1); ++ return new TracingExecutor(Executors.newFixedThreadPool(4, runnable -> { ++ Thread thread = new Thread(runnable); ++ String string2 = namePrefix + atomicInteger.getAndIncrement(); ++ TracyClient.setThreadName(string2, namePrefix.hashCode()); ++ thread.setName(string2); ++ thread.setDaemon(false); + thread.setUncaughtExceptionHandler(Util::onThreadException); + return thread; + })); + } ++ // Paper end - Separate dimension data IO pool + + public static void throwAsRuntime(Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); +@@ -537,7 +583,7 @@ + public static , V> EnumMap makeEnumMap(Class enumClass, Function mapper) { + EnumMap enumMap = new EnumMap<>(enumClass); + +- for (K enum_ : (Enum[])enumClass.getEnumConstants()) { ++ for (K enum_ : enumClass.getEnumConstants()) { // Paper - decompile error + enumMap.put(enum_, mapper.apply(enum_)); + } + +@@ -1057,16 +1103,7 @@ + } + + public void openUri(URI uri) { +- try { +- Process process = AccessController.doPrivileged( +- (PrivilegedExceptionAction)(() -> Runtime.getRuntime().exec(this.getOpenUriArguments(uri))) +- ); +- process.getInputStream().close(); +- process.getErrorStream().close(); +- process.getOutputStream().close(); +- } catch (IOException | PrivilegedActionException var3) { +- Util.LOGGER.error("Couldn't open location '{}'", uri, var3); +- } ++ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code + } + + public void openFile(File file) { diff --git a/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch b/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch new file mode 100644 index 0000000000..d41639df88 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/advancements/AdvancementHolder.java ++++ b/net/minecraft/advancements/AdvancementHolder.java +@@ -5,6 +5,10 @@ + import net.minecraft.network.codec.ByteBufCodecs; + import net.minecraft.network.codec.StreamCodec; + import net.minecraft.resources.ResourceLocation; ++// CraftBukkit start ++import org.bukkit.craftbukkit.advancement.CraftAdvancement; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++// CraftBukkit end + + public record AdvancementHolder(ResourceLocation id, Advancement value) { + +@@ -38,4 +42,10 @@ + public String toString() { + return this.id.toString(); + } ++ ++ // CraftBukkit start ++ public final org.bukkit.advancement.Advancement toBukkit() { ++ return new CraftAdvancement(this); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch b/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch new file mode 100644 index 0000000000..4bec8c46eb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/advancements/AdvancementTree.java ++++ b/net/minecraft/advancements/AdvancementTree.java +@@ -35,7 +35,7 @@ + this.remove(advancementnode1); + } + +- AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder()); ++ AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors + this.nodes.remove(advancement.holder().id()); + if (advancement.parent() == null) { + this.roots.remove(advancement); +@@ -77,7 +77,7 @@ + } + } + +- AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); ++ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement + } + + private boolean tryInsert(AdvancementHolder advancement) { diff --git a/paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch b/paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch new file mode 100644 index 0000000000..a0fdd33f43 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/advancements/DisplayInfo.java ++++ b/net/minecraft/advancements/DisplayInfo.java +@@ -37,6 +37,7 @@ + private final boolean hidden; + private float x; + private float y; ++ public final io.papermc.paper.advancement.AdvancementDisplay paper = new io.papermc.paper.advancement.PaperAdvancementDisplay(this); // Paper - Add more advancement API + + public DisplayInfo( + ItemStack icon, diff --git a/paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch new file mode 100644 index 0000000000..7f7873fc42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/advancements/critereon/LocationPredicate.java ++++ b/net/minecraft/advancements/critereon/LocationPredicate.java +@@ -44,7 +44,7 @@ + public boolean matches(ServerLevel world, double x, double y, double z) { + if (this.position.isPresent() && !this.position.get().matches(x, y, z)) { + return false; +- } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) { ++ } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks + return false; + } else { + BlockPos blockPos = BlockPos.containing(x, y, z); diff --git a/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch new file mode 100644 index 0000000000..d080616002 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -15,41 +15,41 @@ + import net.minecraft.world.level.storage.loot.LootContext; + + public abstract class SimpleCriterionTrigger implements CriterionTrigger { +- private final Map>> players = Maps.newIdentityHashMap(); ++ // private final Map>> players = Maps.newIdentityHashMap(); // Paper - fix AdvancementDataPlayer leak; moved into AdvancementDataPlayer to fix memory leak + + @Override + public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- this.players.computeIfAbsent(manager, managerx -> Sets.newHashSet()).add(conditions); ++ manager.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(conditions); // Paper - fix AdvancementDataPlayer leak + } + + @Override + public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- Set> set = this.players.get(manager); ++ Set> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null) { + set.remove(conditions); + if (set.isEmpty()) { +- this.players.remove(manager); ++ manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + } + } + + @Override + public final void removePlayerListeners(PlayerAdvancements tracker) { +- this.players.remove(tracker); ++ tracker.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + + protected void trigger(ServerPlayer player, Predicate predicate) { + PlayerAdvancements playerAdvancements = player.getAdvancements(); +- Set> set = this.players.get(playerAdvancements); ++ Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null && !set.isEmpty()) { +- LootContext lootContext = EntityPredicate.createContext(player, player); ++ LootContext lootContext = null; // EntityPredicate.createContext(player, player); // Paper - Perf: lazily create LootContext for criterions + List> list = null; + + for (CriterionTrigger.Listener listener : set) { + T simpleInstance = listener.trigger(); + if (predicate.test(simpleInstance)) { + Optional optional = simpleInstance.player(); +- if (optional.isEmpty() || optional.get().matches(lootContext)) { ++ if (optional.isEmpty() || optional.get().matches(lootContext = (lootContext == null ? EntityPredicate.createContext(player, player) : lootContext))) { // Paper - Perf: lazily create LootContext for criterions + if (list == null) { + list = Lists.newArrayList(); + } diff --git a/paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch b/paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch new file mode 100644 index 0000000000..4d74948c36 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/commands/CommandSource.java ++++ b/net/minecraft/commands/CommandSource.java +@@ -22,6 +22,13 @@ + public boolean shouldInformAdmins() { + return false; + } ++ ++ // CraftBukkit start ++ @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return io.papermc.paper.brigadier.NullCommandSender.INSTANCE; // Paper - expose a no-op CommandSender ++ } ++ // CraftBukkit end + }; + + void sendSystemMessage(Component message); +@@ -35,4 +42,6 @@ + default boolean alwaysAccepts() { + return false; + } ++ ++ org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper); // CraftBukkit + } diff --git a/paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch b/paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch new file mode 100644 index 0000000000..94ee38720d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch @@ -0,0 +1,114 @@ +--- a/net/minecraft/commands/CommandSourceStack.java ++++ b/net/minecraft/commands/CommandSourceStack.java +@@ -45,9 +45,9 @@ + import net.minecraft.world.level.dimension.DimensionType; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; ++import com.mojang.brigadier.tree.CommandNode; // CraftBukkit + +-public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider { +- ++public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider, io.papermc.paper.command.brigadier.PaperCommandSourceStack { // Paper - Brigadier API + public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player")); + public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity")); + public final CommandSource source; +@@ -65,6 +65,8 @@ + private final Vec2 rotation; + private final CommandSigningContext signingContext; + private final TaskChainer chatMessageChainer; ++ public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - Thread Safe Vanilla Command permission checking ++ public boolean bypassSelectorPermissions = false; // Paper - add bypass for selector permissions + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { + this(output, pos, rot, world, level, name, displayName, server, entity, false, CommandResultCallback.EMPTY, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(server)); +@@ -171,8 +173,43 @@ + + @Override + public boolean hasPermission(int level) { ++ // CraftBukkit start ++ // Paper start - Thread Safe Vanilla Command permission checking ++ CommandNode currentCommand = this.currentCommand.get(Thread.currentThread()); ++ if (currentCommand != null) { ++ return this.hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand)); ++ // Paper end - Thread Safe Vanilla Command permission checking ++ } ++ // CraftBukkit end ++ + return this.permissionLevel >= level; ++ } ++ ++ // Paper start - Fix permission levels for command blocks ++ private boolean forceRespectPermissionLevel() { ++ return this.source == CommandSource.NULL || (this.source instanceof final net.minecraft.world.level.BaseCommandBlock commandBlock && commandBlock.getLevel().paperConfig().commandBlocks.forceFollowPermLevel); ++ } ++ // Paper end - Fix permission levels for command blocks ++ ++ // CraftBukkit start ++ public boolean hasPermission(int i, String bukkitPermission) { ++ // Paper start - Fix permission levels for command blocks ++ final java.util.function.BooleanSupplier hasBukkitPerm = () -> this.source == CommandSource.NULL /*treat NULL as having all bukkit perms*/ || this.getBukkitSender().hasPermission(bukkitPermission); // lazily check bukkit perms to the benefit of custom permission setups ++ // if the server is null, we must check the vanilla perm level system ++ // if ignoreVanillaPermissions is true, we can skip vanilla perms and just run the bukkit perm check ++ //noinspection ConstantValue ++ if (this.getServer() == null || !this.getServer().server.ignoreVanillaPermissions) { // server & level are null for command function loading ++ final boolean hasPermLevel = this.permissionLevel >= i; ++ if (this.forceRespectPermissionLevel()) { // NULL CommandSource and command blocks (if setting is enabled) should always pass the vanilla perm check ++ return hasPermLevel && hasBukkitPerm.getAsBoolean(); ++ } else { // otherwise check vanilla perm first then bukkit perm, matching upstream behavior ++ return hasPermLevel || hasBukkitPerm.getAsBoolean(); ++ } ++ } ++ return hasBukkitPerm.getAsBoolean(); ++ // Paper end - Fix permission levels for command blocks + } ++ // CraftBukkit end + + public Vec3 getPosition() { + return this.worldPosition; +@@ -302,21 +339,26 @@ + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (entityplayer.commandSource() != this.source && this.server.getPlayerList().isOp(entityplayer.getGameProfile())) { ++ if (entityplayer.commandSource() != this.source && entityplayer.getBukkitEntity().hasPermission("minecraft.admin.command_feedback")) { // CraftBukkit + entityplayer.sendSystemMessage(ichatmutablecomponent); + } + } + } + +- if (this.source != this.server && this.server.getGameRules().getBoolean(GameRules.RULE_LOGADMINCOMMANDS)) { ++ if (this.source != this.server && this.server.getGameRules().getBoolean(GameRules.RULE_LOGADMINCOMMANDS) && !org.spigotmc.SpigotConfig.silentCommandBlocks) { // Spigot + this.server.sendSystemMessage(ichatmutablecomponent); + } + + } + + public void sendFailure(Component message) { ++ // Paper start - Add UnknownCommandEvent ++ this.sendFailure(message, true); ++ } ++ public void sendFailure(Component message, boolean withStyle) { ++ // Paper end - Add UnknownCommandEvent + if (this.source.acceptsFailure() && !this.silent) { +- this.source.sendSystemMessage(Component.empty().append(message).withStyle(ChatFormatting.RED)); ++ this.source.sendSystemMessage(withStyle ? Component.empty().append(message).withStyle(ChatFormatting.RED) : message); // Paper - Add UnknownCommandEvent + } + + } +@@ -400,4 +442,16 @@ + public boolean isSilent() { + return this.silent; + } ++ ++ // Paper start ++ @Override ++ public CommandSourceStack getHandle() { ++ return this; ++ } ++ // Paper end ++ // CraftBukkit start ++ public org.bukkit.command.CommandSender getBukkitSender() { ++ return this.source.getBukkitSender(this); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch new file mode 100644 index 0000000000..1fc19d5217 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch @@ -0,0 +1,372 @@ +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -138,6 +138,14 @@ + import net.minecraft.world.flag.FeatureFlags; + import net.minecraft.world.level.GameRules; + import org.slf4j.Logger; ++ ++// CraftBukkit start ++import com.google.common.base.Joiner; ++import java.util.Collection; ++import java.util.LinkedHashSet; ++import org.bukkit.event.player.PlayerCommandSendEvent; ++import org.bukkit.event.server.ServerCommandEvent; ++// CraftBukkit end + + public class Commands { + +@@ -151,6 +159,7 @@ + private final com.mojang.brigadier.CommandDispatcher dispatcher = new com.mojang.brigadier.CommandDispatcher(); + + public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) { ++ // Paper + AdvancementCommands.register(this.dispatcher); + AttributeCommand.register(this.dispatcher, commandRegistryAccess); + ExecuteCommand.register(this.dispatcher, commandRegistryAccess); +@@ -250,8 +259,33 @@ + + if (environment.includeIntegrated) { + PublishCommand.register(this.dispatcher); ++ } ++ ++ // Paper start - Vanilla command permission fixes ++ for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { ++ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { ++ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node)); ++ } + } ++ // Paper end - Vanilla command permission fixes ++ // Paper start - Brigadier Command API ++ // Create legacy minecraft namespace commands ++ for (final CommandNode node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) { ++ // The brigadier dispatcher is not able to resolve nested redirects. ++ // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport. ++ // Instead, target the first none redirecting node. ++ CommandNode flattenedAliasTarget = node; ++ while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect(); + ++ this.dispatcher.register( ++ com.mojang.brigadier.builder.LiteralArgumentBuilder.literal("minecraft:" + node.getName()) ++ .executes(flattenedAliasTarget.getCommand()) ++ .requires(flattenedAliasTarget.getRequirement()) ++ .redirect(flattenedAliasTarget) ++ ); ++ } ++ // Paper end - Brigadier Command API ++ // Paper - remove public constructor, no longer needed + this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer()); + } + +@@ -262,30 +296,72 @@ + return new ParseResults(commandcontextbuilder1, parseResults.getReader(), parseResults.getExceptions()); + } + ++ // CraftBukkit start ++ public void dispatchServerCommand(CommandSourceStack sender, String command) { ++ Joiner joiner = Joiner.on(" "); ++ if (command.startsWith("/")) { ++ command = command.substring(1); ++ } ++ ++ ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ command = event.getCommand(); ++ ++ String[] args = command.split(" "); ++ if (args.length == 0) return; // Paper - empty commands shall not be dispatched ++ ++ // Paper - Fix permission levels for command blocks ++ ++ // Handle vanilla commands; // Paper - handled in CommandNode/CommandDispatcher ++ ++ String newCommand = joiner.join(args); ++ this.performPrefixedCommand(sender, newCommand, newCommand); ++ } ++ // CraftBukkit end ++ + public void performPrefixedCommand(CommandSourceStack source, String command) { +- command = command.startsWith("/") ? command.substring(1) : command; +- this.performCommand(this.dispatcher.parse(command, source), command); ++ // CraftBukkit start ++ this.performPrefixedCommand(source, command, command); ++ } ++ ++ public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s, String label) { ++ s = s.startsWith("/") ? s.substring(1) : s; ++ this.performCommand(this.dispatcher.parse(s, commandlistenerwrapper), s, label); ++ // CraftBukkit end + } + + public void performCommand(ParseResults parseResults, String command) { +- CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseResults.getContext().getSource(); ++ this.performCommand(parseResults, command, command); ++ } + ++ public void performCommand(ParseResults parseresults, String s, String label) { // CraftBukkit ++ // Paper start ++ this.performCommand(parseresults, s, label, false); ++ } ++ public void performCommand(ParseResults parseresults, String s, String label, boolean throwCommandError) { ++ // Paper end ++ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource(); ++ + Profiler.get().push(() -> { +- return "/" + command; ++ return "/" + s; + }); +- ContextChain contextchain = Commands.finishParsing(parseResults, command, commandlistenerwrapper); ++ ContextChain contextchain = this.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit // Paper - Add UnknownCommandEvent + + try { + if (contextchain != null) { + Commands.executeCommandInContext(commandlistenerwrapper, (executioncontext) -> { +- ExecutionContext.queueInitialCommandExecution(executioncontext, command, contextchain, commandlistenerwrapper, CommandResultCallback.EMPTY); ++ ExecutionContext.queueInitialCommandExecution(executioncontext, s, contextchain, commandlistenerwrapper, CommandResultCallback.EMPTY); + }); + } + } catch (Exception exception) { ++ if (throwCommandError) throw exception; + MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); + +- if (Commands.LOGGER.isDebugEnabled()) { +- Commands.LOGGER.error("Command exception: /{}", command, exception); ++ Commands.LOGGER.error("Command exception: /{}", s, exception); // Paper - always show execution exception in console log ++ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging + StackTraceElement[] astacktraceelement = exception.getStackTrace(); + + for (int i = 0; i < Math.min(astacktraceelement.length, 3); ++i) { +@@ -298,7 +374,7 @@ + })); + if (SharedConstants.IS_RUNNING_IN_IDE) { + commandlistenerwrapper.sendFailure(Component.literal(Util.describeError(exception))); +- Commands.LOGGER.error("'/{}' threw an exception", command, exception); ++ Commands.LOGGER.error("'/{}' threw an exception", s, exception); + } + } finally { + Profiler.get().pop(); +@@ -307,18 +383,22 @@ + } + + @Nullable +- private static ContextChain finishParsing(ParseResults parseResults, String command, CommandSourceStack source) { ++ private ContextChain finishParsing(ParseResults parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent + try { +- Commands.validateParseResults(parseResults); +- return (ContextChain) ContextChain.tryFlatten(parseResults.getContext().build(command)).orElseThrow(() -> { +- return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseResults.getReader()); ++ Commands.validateParseResults(parseresults); ++ return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> { ++ return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader()); + }); + } catch (CommandSyntaxException commandsyntaxexception) { +- source.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); ++ // Paper start - Add UnknownCommandEvent ++ final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); ++ // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); ++ builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage())); ++ // Paper end - Add UnknownCommandEvent + if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { + int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); + MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { +- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command)); ++ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper + }); + + if (i > 10) { +@@ -333,8 +413,18 @@ + } + + ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); +- source.sendFailure(ichatmutablecomponent); ++ // Paper start - Add UnknownCommandEvent ++ // commandlistenerwrapper.sendFailure(ichatmutablecomponent); ++ builder ++ .append(net.kyori.adventure.text.Component.newline()) ++ .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); + } ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.message() != null) { ++ commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); ++ // Paper end - Add UnknownCommandEvent ++ } + + return null; + } +@@ -368,7 +458,7 @@ + + executioncontext1.close(); + } finally { +- Commands.CURRENT_EXECUTION_CONTEXT.set((Object) null); ++ Commands.CURRENT_EXECUTION_CONTEXT.set(null); // CraftBukkit - decompile error + } + } else { + callback.accept(executioncontext); +@@ -377,30 +467,133 @@ + } + + public void sendCommands(ServerPlayer player) { +- Map, CommandNode> map = Maps.newHashMap(); ++ // Paper start - Send empty commands if tab completion is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); ++ return; ++ } ++ // Paper end - Send empty commands if tab completion is disabled ++ // CraftBukkit start ++ // Register Vanilla commands into builtRoot as before ++ // Paper start - Perf: Async command map building ++ // Copy root children to avoid concurrent modification during building ++ final Collection> commandNodes = new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren()); ++ COMMAND_SENDING_POOL.execute(() -> this.sendAsync(player, commandNodes)); ++ } ++ ++ // Fixed pool, but with discard policy ++ public static final java.util.concurrent.ExecutorService COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor( ++ 2, 2, 0, java.util.concurrent.TimeUnit.MILLISECONDS, ++ new java.util.concurrent.LinkedBlockingQueue<>(), ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Paper Async Command Builder Thread Pool - %1$d") ++ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) ++ .build(), ++ new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() ++ ); ++ ++ private void sendAsync(ServerPlayer player, Collection> dispatcherRootChildren) { ++ // Paper end - Perf: Async command map building ++ Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues ++ // Paper - brigadier API removes the need to fill the map twice + RootCommandNode rootcommandnode = new RootCommandNode(); + + map.put(this.dispatcher.getRoot(), rootcommandnode); +- this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, player.createCommandSourceStack(), map); ++ this.fillUsableCommands(dispatcherRootChildren, rootcommandnode, player.createCommandSourceStack(), map); // Paper - Perf: Async command map building; pass copy of children ++ ++ Collection bukkit = new LinkedHashSet<>(); ++ for (CommandNode node : rootcommandnode.getChildren()) { ++ bukkit.add(node.getName()); ++ } ++ // Paper start - Perf: Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API ++ net.minecraft.server.MinecraftServer.getServer().execute(() -> { ++ runSync(player, bukkit, rootcommandnode); ++ }); ++ } ++ ++ private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { ++ // Paper end - Perf: Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API ++ PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); ++ event.getPlayer().getServer().getPluginManager().callEvent(event); ++ ++ // Remove labels that were removed during the event ++ for (String orig : bukkit) { ++ if (!event.getCommands().contains(orig)) { ++ rootcommandnode.removeCommand(orig); ++ } ++ } ++ // CraftBukkit end + player.connection.send(new ClientboundCommandsPacket(rootcommandnode)); + } + +- private void fillUsableCommands(CommandNode tree, CommandNode result, CommandSourceStack source, Map, CommandNode> resultNodes) { +- Iterator iterator = tree.getChildren().iterator(); ++ // Paper start - Perf: Async command map building; pass copy of children ++ private void fillUsableCommands(Collection> children, CommandNode result, CommandSourceStack source, Map, CommandNode> resultNodes) { ++ resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below ++ Iterator iterator = children.iterator(); ++ // Paper end - Perf: Async command map building + + while (iterator.hasNext()) { + CommandNode commandnode2 = (CommandNode) iterator.next(); ++ // Paper start - Brigadier API ++ if (commandnode2.clientNode != null) { ++ commandnode2 = commandnode2.clientNode; ++ } ++ // Paper end - Brigadier API ++ if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot + + if (commandnode2.canUse(source)) { +- ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); ++ ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); // CraftBukkit - decompile error ++ // Paper start ++ /* ++ Because of how commands can be yeeted right left and center due to bad bukkit practices ++ we need to be able to ensure that ALL commands are registered (even redirects). + ++ What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten) ++ all the children from the dead redirect to the node. ++ ++ So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children. ++ ++ The only way to fix this is to either: ++ - Send EVERYTHING flattened, don't use redirects ++ - Don't allow command nodes to be deleted ++ - Do this :) ++ */ ++ ++ // Is there an invalid command redirect? ++ if (argumentbuilder.getRedirect() != null && (CommandNode) resultNodes.get(argumentbuilder.getRedirect()) == null) { ++ // Create the argument builder with the same values as the specified node, but with a different literal and populated children ++ ++ CommandNode redirect = argumentbuilder.getRedirect(); ++ // Diff copied from LiteralCommand#createBuilder ++ final com.mojang.brigadier.builder.LiteralArgumentBuilder builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandnode2.getName()); ++ builder.requires(redirect.getRequirement()); ++ // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid. ++ if (redirect.getCommand() != null) { ++ builder.executes(redirect.getCommand()); ++ } ++ // Diff copied from LiteralCommand#createBuilder ++ for (CommandNode child : redirect.getChildren()) { ++ builder.then(child); ++ } ++ ++ argumentbuilder = builder; ++ } ++ // Paper end ++ + argumentbuilder.requires((icompletionprovider) -> { + return true; + }); + if (argumentbuilder.getCommand() != null) { +- argumentbuilder.executes((commandcontext) -> { +- return 0; ++ // Paper start - fix suggestions due to falsely equal nodes ++ argumentbuilder.executes(new com.mojang.brigadier.Command() { ++ @Override ++ public int run(com.mojang.brigadier.context.CommandContext commandContext) throws CommandSyntaxException { ++ return 0; ++ } + }); ++ // Paper end + } + + if (argumentbuilder instanceof RequiredArgumentBuilder) { +@@ -415,12 +608,12 @@ + argumentbuilder.redirect((CommandNode) resultNodes.get(argumentbuilder.getRedirect())); + } + +- CommandNode commandnode3 = argumentbuilder.build(); ++ CommandNode commandnode3 = argumentbuilder.build(); // CraftBukkit - decompile error + + resultNodes.put(commandnode2, commandnode3); + result.addChild(commandnode3); + if (!commandnode2.getChildren().isEmpty()) { +- this.fillUsableCommands(commandnode2, commandnode3, source, resultNodes); ++ this.fillUsableCommands(commandnode2.getChildren(), commandnode3, source, resultNodes); // Paper - Perf: Async command map building; pass children directly + } + } + } +@@ -481,7 +674,7 @@ + } + + private HolderLookup.RegistryLookup.Delegate createLookup(final HolderLookup.RegistryLookup original) { +- return new HolderLookup.RegistryLookup.Delegate(this) { ++ return new HolderLookup.RegistryLookup.Delegate() { // CraftBukkit - decompile error + @Override + public HolderLookup.RegistryLookup parent() { + return original; diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch new file mode 100644 index 0000000000..9c45cfda8f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/commands/arguments/EntityArgument.java ++++ b/net/minecraft/commands/arguments/EntityArgument.java +@@ -102,21 +102,27 @@ + } + + private EntitySelector parse(StringReader reader, boolean allowAtSelectors) throws CommandSyntaxException { ++ // CraftBukkit start ++ return this.parse(reader, allowAtSelectors, false); ++ } ++ ++ public EntitySelector parse(StringReader stringreader, boolean flag, boolean overridePermissions) throws CommandSyntaxException { ++ // CraftBukkit end + boolean flag1 = false; +- EntitySelectorParser argumentparserselector = new EntitySelectorParser(reader, allowAtSelectors); +- EntitySelector entityselector = argumentparserselector.parse(); ++ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, flag); ++ EntitySelector entityselector = argumentparserselector.parse(overridePermissions); // CraftBukkit + + if (entityselector.getMaxResults() > 1 && this.single) { + if (this.playersOnly) { +- reader.setCursor(0); +- throw EntityArgument.ERROR_NOT_SINGLE_PLAYER.createWithContext(reader); ++ stringreader.setCursor(0); ++ throw EntityArgument.ERROR_NOT_SINGLE_PLAYER.createWithContext(stringreader); + } else { +- reader.setCursor(0); +- throw EntityArgument.ERROR_NOT_SINGLE_ENTITY.createWithContext(reader); ++ stringreader.setCursor(0); ++ throw EntityArgument.ERROR_NOT_SINGLE_ENTITY.createWithContext(stringreader); + } + } else if (entityselector.includesEntities() && this.playersOnly && !entityselector.isSelfSelector()) { +- reader.setCursor(0); +- throw EntityArgument.ERROR_ONLY_PLAYERS_ALLOWED.createWithContext(reader); ++ stringreader.setCursor(0); ++ throw EntityArgument.ERROR_ONLY_PLAYERS_ALLOWED.createWithContext(stringreader); + } else { + return entityselector; + } +@@ -129,7 +135,12 @@ + StringReader stringreader = new StringReader(suggestionsbuilder.getInput()); + + stringreader.setCursor(suggestionsbuilder.getStart()); +- EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, EntitySelectorParser.allowSelectors(icompletionprovider)); ++ // Paper start - Fix EntityArgument permissions ++ final boolean permission = object instanceof CommandSourceStack stack ++ ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") ++ : icompletionprovider.hasPermission(2); ++ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission); ++ // Paper end - Fix EntityArgument permissions + + try { + argumentparserselector.parse(); diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch new file mode 100644 index 0000000000..ba048d7f68 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/commands/arguments/MessageArgument.java ++++ b/net/minecraft/commands/arguments/MessageArgument.java +@@ -40,6 +40,11 @@ + + public static void resolveChatMessage(CommandContext context, String name, Consumer callback) throws CommandSyntaxException { + MessageArgument.Message message = context.getArgument(name, MessageArgument.Message.class); ++ // Paper start ++ resolveChatMessage(message, context, name, callback); ++ } ++ public static void resolveChatMessage(MessageArgument.Message message, CommandContext context, String name, Consumer callback) throws CommandSyntaxException { ++ // Paper end + CommandSourceStack commandSourceStack = context.getSource(); + Component component = message.resolveComponent(commandSourceStack); + CommandSigningContext commandSigningContext = commandSourceStack.getSigningContext(); +@@ -54,17 +59,21 @@ + private static void resolveSignedMessage(Consumer callback, CommandSourceStack source, PlayerChatMessage message) { + MinecraftServer minecraftServer = source.getServer(); + CompletableFuture completableFuture = filterPlainText(source, message); +- Component component = minecraftServer.getChatDecorator().decorate(source.getPlayer(), message.decoratedContent()); +- source.getChatMessageChainer().append(completableFuture, filtered -> { +- PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(component).filter(filtered.mask()); ++ // Paper start - support asynchronous chat decoration ++ CompletableFuture componentFuture = minecraftServer.getChatDecorator().decorate(source.getPlayer(), source, message.decoratedContent()); ++ source.getChatMessageChainer().append(CompletableFuture.allOf(completableFuture, componentFuture), filtered -> { ++ PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask()); ++ // Paper end - support asynchronous chat decoration + callback.accept(playerChatMessage2); + }); + } + + private static void resolveDisguisedMessage(Consumer callback, CommandSourceStack source, PlayerChatMessage message) { + ChatDecorator chatDecorator = source.getServer().getChatDecorator(); +- Component component = chatDecorator.decorate(source.getPlayer(), message.decoratedContent()); +- callback.accept(message.withUnsignedContent(component)); ++ // Paper start - support asynchronous chat decoration ++ CompletableFuture componentFuture = chatDecorator.decorate(source.getPlayer(), source, message.decoratedContent()); ++ source.getChatMessageChainer().append(componentFuture, (result) -> callback.accept(message.withUnsignedContent(result))); ++ // Paper end - support asynchronous chat decoration + } + + private static CompletableFuture filterPlainText(CommandSourceStack source, PlayerChatMessage message) { diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch new file mode 100644 index 0000000000..46961c4f64 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/commands/arguments/blocks/BlockStateParser.java ++++ b/net/minecraft/commands/arguments/blocks/BlockStateParser.java +@@ -67,7 +67,7 @@ + private final StringReader reader; + private final boolean forTesting; + private final boolean allowNbt; +- private final Map, Comparable> properties = Maps.newHashMap(); ++ private final Map, Comparable> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable + private final Map vagueProperties = Maps.newHashMap(); + private ResourceLocation id = ResourceLocation.withDefaultNamespace(""); + @Nullable +@@ -275,7 +275,7 @@ + Iterator iterator = property.getPossibleValues().iterator(); + + while (iterator.hasNext()) { +- T t0 = (Comparable) iterator.next(); ++ T t0 = (T) iterator.next(); // CraftBukkit - decompile error + + if (t0 instanceof Integer integer) { + builder.suggest(integer); +@@ -545,7 +545,7 @@ + Optional optional = property.getValue(value); + + if (optional.isPresent()) { +- this.state = (BlockState) this.state.setValue(property, (Comparable) optional.get()); ++ this.state = (BlockState) this.state.setValue(property, (T) optional.get()); // CraftBukkit - decompile error + this.properties.put(property, (Comparable) optional.get()); + } else { + this.reader.setCursor(cursor); +@@ -581,7 +581,7 @@ + private static > void appendProperty(StringBuilder builder, Property property, Comparable value) { + builder.append(property.getName()); + builder.append('='); +- builder.append(property.getName(value)); ++ builder.append(property.getName((T) value)); // CraftBukkit - decompile error + } + + public static record BlockResult(BlockState blockState, Map, Comparable> properties, @Nullable CompoundTag nbt) { diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/item/ItemInput.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/item/ItemInput.java.patch new file mode 100644 index 0000000000..a20c412208 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/item/ItemInput.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/commands/arguments/item/ItemInput.java ++++ b/net/minecraft/commands/arguments/item/ItemInput.java +@@ -78,6 +78,6 @@ + } + + private String getItemName() { +- return this.item.unwrapKey().map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString(); ++ return this.item.unwrapKey().map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString(); // Paper - decompile fix + } + } diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch new file mode 100644 index 0000000000..cb23767a27 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/commands/arguments/selector/EntitySelector.java ++++ b/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -93,7 +93,7 @@ + } + + private void checkPermissions(CommandSourceStack source) throws CommandSyntaxException { +- if (this.usesSelector && !source.hasPermission(2)) { ++ if (!source.bypassSelectorPermissions && (this.usesSelector && !source.hasPermission(2, "minecraft.command.selector"))) { // CraftBukkit // Paper - add bypass for selector perms + throw EntityArgument.ERROR_SELECTORS_NOT_ALLOWED.create(); + } + } diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch new file mode 100644 index 0000000000..088ffce992 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java ++++ b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java +@@ -133,7 +133,7 @@ + boolean flag; + + if (source instanceof SharedSuggestionProvider icompletionprovider) { +- if (icompletionprovider.hasPermission(2)) { ++ if (source instanceof net.minecraft.commands.CommandSourceStack stack ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") : icompletionprovider.hasPermission(2)) { // Paper - Fix EntityArgument permissions + flag = true; + return flag; + } +@@ -158,7 +158,7 @@ + axisalignedbb = this.createAabb(this.deltaX == null ? 0.0D : this.deltaX, this.deltaY == null ? 0.0D : this.deltaY, this.deltaZ == null ? 0.0D : this.deltaZ); + } + +- Function function; ++ Function function; // CraftBukkit - decompile error + + if (this.x == null && this.y == null && this.z == null) { + function = (vec3d) -> { +@@ -215,8 +215,10 @@ + }; + } + +- protected void parseSelector() throws CommandSyntaxException { +- this.usesSelectors = true; ++ // CraftBukkit start ++ protected void parseSelector(boolean overridePermissions) throws CommandSyntaxException { ++ this.usesSelectors = !overridePermissions; ++ // CraftBukkit end + this.suggestions = this::suggestSelector; + if (!this.reader.canRead()) { + throw EntitySelectorParser.ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader); +@@ -505,6 +507,12 @@ + } + + public EntitySelector parse() throws CommandSyntaxException { ++ // CraftBukkit start ++ return this.parse(false); ++ } ++ ++ public EntitySelector parse(boolean overridePermissions) throws CommandSyntaxException { ++ // CraftBukkit end + this.startPosition = this.reader.getCursor(); + this.suggestions = this::suggestNameOrSelector; + if (this.reader.canRead() && this.reader.peek() == '@') { +@@ -513,7 +521,7 @@ + } + + this.reader.skip(); +- this.parseSelector(); ++ this.parseSelector(overridePermissions); // CraftBukkit + } else { + this.parseNameOrUUID(); + } diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch new file mode 100644 index 0000000000..ed6f4da14b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/commands/arguments/selector/SelectorPattern.java ++++ b/net/minecraft/commands/arguments/selector/SelectorPattern.java +@@ -13,7 +13,7 @@ + EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(selector), true); + return DataResult.success(new SelectorPattern(selector, entitySelectorParser.parse())); + } catch (CommandSyntaxException var2) { +- return DataResult.error(() -> "Invalid selector component: " + selector + ": " + var2.getMessage()); ++ return DataResult.error(() -> "Invalid selector component"); // Paper - limit selector error message + } + } + diff --git a/paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch b/paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch new file mode 100644 index 0000000000..7858f6cef1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch @@ -0,0 +1,186 @@ +--- a/net/minecraft/core/BlockPos.java ++++ b/net/minecraft/core/BlockPos.java +@@ -158,67 +158,84 @@ + + @Override + public BlockPos above() { +- return this.relative(Direction.UP); ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos above(int distance) { +- return this.relative(Direction.UP, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos below() { +- return this.relative(Direction.DOWN); ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos below(int i) { +- return this.relative(Direction.DOWN, i); ++ return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos north() { +- return this.relative(Direction.NORTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos north(int distance) { +- return this.relative(Direction.NORTH, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos south() { +- return this.relative(Direction.SOUTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos south(int distance) { +- return this.relative(Direction.SOUTH, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos west() { +- return this.relative(Direction.WEST); ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos west(int distance) { +- return this.relative(Direction.WEST, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos east() { +- return this.relative(Direction.EAST); ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos east(int distance) { +- return this.relative(Direction.EAST, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos relative(Direction direction) { ++ // Paper start - Perf: Optimize BlockPosition ++ switch(direction) { ++ case UP: ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); ++ case DOWN: ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); ++ case NORTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); ++ case SOUTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); ++ case WEST: ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); ++ case EAST: ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); ++ default: + return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); ++ } ++ // Paper end - Perf: Optimize BlockPosition + } + + @Override +@@ -324,9 +341,11 @@ + + public static Iterable withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) { + int i = rangeX + rangeY + rangeZ; +- int j = center.getX(); +- int k = center.getY(); +- int l = center.getZ(); ++ // Paper start - rename variables to fix conflict with anonymous class (remap fix) ++ int centerX = center.getX(); ++ int centerY = center.getY(); ++ int centerZ = center.getZ(); ++ // Paper end + return () -> new AbstractIterator() { + private final BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); + private int currentDepth; +@@ -340,7 +359,7 @@ + protected BlockPos computeNext() { + if (this.zMirror) { + this.zMirror = false; +- this.cursor.setZ(l - (this.cursor.getZ() - l)); ++ this.cursor.setZ(centerZ - (this.cursor.getZ() - centerZ)); // Paper - remap fix + return this.cursor; + } else { + BlockPos blockPos; +@@ -366,7 +385,7 @@ + int k = this.currentDepth - Math.abs(i) - Math.abs(j); + if (k <= rangeZ) { + this.zMirror = k != 0; +- blockPos = this.cursor.set(j + i, k + j, l + k); ++ blockPos = this.cursor.set(centerX + i, centerY + j, centerZ + k); // Paper - remap fix + } + } + +@@ -444,12 +463,12 @@ + if (this.index == l) { + return this.endOfData(); + } else { +- int i = this.index % i; +- int j = this.index / i; +- int k = j % j; +- int l = j / j; ++ int offsetX = this.index % i; // Paper - decomp fix ++ int u = this.index / i; // Paper - decomp fix ++ int offsetY = u % j; // Paper - decomp fix ++ int offsetZ = u / j; // Paper - decomp fix + this.index++; +- return this.cursor.set(startX + i, startY + k, startZ + l); ++ return this.cursor.set(startX + offsetX, startY + offsetY, startZ + offsetZ); // Paper - decomp fix + } + } + }; +@@ -569,9 +588,9 @@ + } + + public BlockPos.MutableBlockPos set(int x, int y, int z) { +- this.setX(x); +- this.setY(y); +- this.setZ(z); ++ this.x = x; // Paper - Perf: Manually inline methods in BlockPosition ++ this.y = y; // Paper - Perf: Manually inline methods in BlockPosition ++ this.z = z; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + +@@ -636,19 +655,19 @@ + + @Override + public BlockPos.MutableBlockPos setX(int i) { +- super.setX(i); ++ this.x = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + + @Override + public BlockPos.MutableBlockPos setY(int i) { +- super.setY(i); ++ this.y = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + + @Override + public BlockPos.MutableBlockPos setZ(int i) { +- super.setZ(i); ++ this.z = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + diff --git a/paper-server/patches/sources/net/minecraft/core/Direction.java.patch b/paper-server/patches/sources/net/minecraft/core/Direction.java.patch new file mode 100644 index 0000000000..a0a77c5f10 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/Direction.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/core/Direction.java ++++ b/net/minecraft/core/Direction.java +@@ -57,6 +57,12 @@ + .sorted(Comparator.comparingInt(direction -> direction.data2d)) + .toArray(Direction[]::new); + ++ // Paper start - Perf: Inline shift direction fields ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end - Perf: Inline shift direction fields ++ + private Direction( + final int id, + final int idOpposite, +@@ -74,6 +80,11 @@ + this.axisDirection = direction; + this.normal = vector; + this.normalVec3 = Vec3.atLowerCornerOf(vector); ++ // Paper start - Perf: Inline shift direction fields ++ this.adjX = vector.getX(); ++ this.adjY = vector.getY(); ++ this.adjZ = vector.getZ(); ++ // Paper end - Perf: Inline shift direction fields + } + + public static Direction[] orderedByNearest(Entity entity) { +@@ -247,15 +258,15 @@ + } + + public int getStepX() { +- return this.normal.getX(); ++ return this.adjX; // Paper - Perf: Inline shift direction fields + } + + public int getStepY() { +- return this.normal.getY(); ++ return this.adjY; // Paper - Perf: Inline shift direction fields + } + + public int getStepZ() { +- return this.normal.getZ(); ++ return this.adjZ; // Paper - Perf: Inline shift direction fields + } + + public Vector3f step() { diff --git a/paper-server/patches/sources/net/minecraft/core/Holder.java.patch b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch new file mode 100644 index 0000000000..634a2b2190 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/core/Holder.java ++++ b/net/minecraft/core/Holder.java +@@ -230,7 +230,7 @@ + } + + void bindTags(Collection> tags) { +- this.tags = Set.copyOf(tags); ++ this.tags = java.util.Collections.unmodifiableSet(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch b/paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch new file mode 100644 index 0000000000..4d3c8b552e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/core/MappedRegistry.java ++++ b/net/minecraft/core/MappedRegistry.java +@@ -33,11 +33,11 @@ + public class MappedRegistry implements WritableRegistry { + private final ResourceKey> key; + private final ObjectList> byId = new ObjectArrayList<>(256); +- private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); +- private final Map> byLocation = new HashMap<>(); +- private final Map, Holder.Reference> byKey = new HashMap<>(); +- private final Map> byValue = new IdentityHashMap<>(); +- private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(); ++ private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(2048), map -> map.defaultReturnValue(-1)); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map> byLocation = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map, Holder.Reference> byKey = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map> byValue = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions + private Lifecycle registryLifecycle; + private final Map, HolderSet.Named> frozenTags = new IdentityHashMap<>(); + MappedRegistry.TagSet allTags = MappedRegistry.TagSet.unbound(); +@@ -508,5 +508,13 @@ + void forEach(BiConsumer, ? super HolderSet.Named> consumer); + + Stream> getTags(); ++ } ++ // Paper start ++ // used to clear intrusive holders from GameEvent, Item, Block, EntityType, and Fluid from unused instances of those types ++ public void clearIntrusiveHolder(final T instance) { ++ if (this.unregisteredIntrusiveHolders != null) { ++ this.unregisteredIntrusiveHolders.remove(instance); ++ } + } ++ // Paper end + } diff --git a/paper-server/patches/sources/net/minecraft/core/Rotations.java.patch b/paper-server/patches/sources/net/minecraft/core/Rotations.java.patch new file mode 100644 index 0000000000..29c5bfc220 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/Rotations.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/core/Rotations.java ++++ b/net/minecraft/core/Rotations.java +@@ -34,6 +34,18 @@ + this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2)); + } + ++ // Paper start - faster alternative constructor ++ private Rotations(float x, float y, float z, Void dummy_var) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ } ++ ++ public static Rotations createWithoutValidityChecks(float x, float y, float z) { ++ return new Rotations(x, y, z, null); ++ } ++ // Paper end - faster alternative constructor ++ + public ListTag save() { + ListTag listTag = new ListTag(); + listTag.add(FloatTag.valueOf(this.x)); diff --git a/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch new file mode 100644 index 0000000000..8c19b63ae6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/core/Vec3i.java ++++ b/net/minecraft/core/Vec3i.java +@@ -16,9 +16,9 @@ + vec -> IntStream.of(vec.getX(), vec.getY(), vec.getZ()) + ); + public static final Vec3i ZERO = new Vec3i(0, 0, 0); +- private int x; +- private int y; +- private int z; ++ protected int x; // Paper - Perf: Manually inline methods in BlockPosition; protected ++ protected int y; // Paper - Perf: Manually inline methods in BlockPosition; protected ++ protected int z; // Paper - Perf: Manually inline methods in BlockPosition; protected + + public static Codec offsetCodec(int maxAbsValue) { + return CODEC.validate( +@@ -35,12 +35,12 @@ + } + + @Override +- public boolean equals(Object object) { ++ public final boolean equals(Object object) { // Paper - Perf: Final for inline + return this == object || object instanceof Vec3i vec3i && this.getX() == vec3i.getX() && this.getY() == vec3i.getY() && this.getZ() == vec3i.getZ(); + } + + @Override +- public int hashCode() { ++ public final int hashCode() { // Paper - Perf: Final for inline + return (this.getY() + this.getZ() * 31) * 31 + this.getX(); + } + +@@ -53,15 +53,15 @@ + } + } + +- public int getX() { ++ public final int getX() { // Paper - Perf: Final for inline + return this.x; + } + +- public int getY() { ++ public final int getY() { // Paper - Perf: Final for inline + return this.y; + } + +- public int getZ() { ++ public final int getZ() { // Paper - Perf: Final for inline + return this.z; + } + diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch new file mode 100644 index 0000000000..f0db760867 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch @@ -0,0 +1,330 @@ +--- a/net/minecraft/core/cauldron/CauldronInteraction.java ++++ b/net/minecraft/core/cauldron/CauldronInteraction.java +@@ -35,20 +35,25 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.material.FluidState; ++// CraftBukkit start ++import org.bukkit.event.block.CauldronLevelChangeEvent; ++// CraftBukkit end + + public interface CauldronInteraction { + + Map INTERACTIONS = new Object2ObjectArrayMap(); +- Codec CODEC; +- CauldronInteraction.InteractionMap EMPTY; +- CauldronInteraction.InteractionMap WATER; +- CauldronInteraction.InteractionMap LAVA; +- CauldronInteraction.InteractionMap POWDER_SNOW; ++ // CraftBukkit start - decompile errors ++ Codec CODEC = Codec.stringResolver(CauldronInteraction.InteractionMap::name, CauldronInteraction.INTERACTIONS::get); ++ CauldronInteraction.InteractionMap EMPTY = CauldronInteraction.newInteractionMap("empty"); ++ CauldronInteraction.InteractionMap WATER = CauldronInteraction.newInteractionMap("water"); ++ CauldronInteraction.InteractionMap LAVA = CauldronInteraction.newInteractionMap("lava"); ++ CauldronInteraction.InteractionMap POWDER_SNOW = CauldronInteraction.newInteractionMap("powder_snow"); ++ // CraftBukkit end + + static CauldronInteraction.InteractionMap newInteractionMap(String name) { + Object2ObjectOpenHashMap object2objectopenhashmap = new Object2ObjectOpenHashMap(); + +- object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + return InteractionResult.TRY_WITH_EMPTY_HAND; + }); + CauldronInteraction.InteractionMap cauldroninteraction_a = new CauldronInteraction.InteractionMap(name, object2objectopenhashmap); +@@ -57,23 +62,28 @@ + return cauldroninteraction_a; + } + +- InteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack); ++ InteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection); // Paper - add hitDirection + + static void bootStrap() { + Map map = CauldronInteraction.EMPTY.map(); + + CauldronInteraction.addDefaultInteractions(map); +- map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS); + + if (potioncontents != null && potioncontents.is(Potions.WATER)) { + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + Item item = itemstack.getItem(); + + entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE))); + entityhuman.awardStat(Stats.USE_CAULDRON); + entityhuman.awardStat(Stats.ITEM_USED.get(item)); +- world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); ++ // world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit + world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F); + world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, blockposition); + } +@@ -86,26 +96,31 @@ + Map map1 = CauldronInteraction.WATER.map(); + + CauldronInteraction.addDefaultInteractions(map1); +- map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.WATER_BUCKET), (iblockdata1) -> { + return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3; +- }, SoundEvents.BUCKET_FILL); ++ }, SoundEvents.BUCKET_FILL, hitDirection); // Paper - add hitDirection + }); +- map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + Item item = itemstack.getItem(); + + entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, PotionContents.createItemStack(Items.POTION, Potions.WATER))); + entityhuman.awardStat(Stats.USE_CAULDRON); + entityhuman.awardStat(Stats.ITEM_USED.get(item)); +- LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); ++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit + world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F); + world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, blockposition); + } + + return InteractionResult.SUCCESS; + }); +- map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + if ((Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) == 3) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else { +@@ -113,10 +128,15 @@ + + if (potioncontents != null && potioncontents.is(Potions.WATER)) { + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE))); + entityhuman.awardStat(Stats.USE_CAULDRON); + entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); +- world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.cycle(LayeredCauldronBlock.LEVEL)); ++ // world.setBlockAndUpdate(blockposition, (IBlockData) iblockdata.cycle(LayeredCauldronBlock.LEVEL)); // CraftBukkit + world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F); + world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, blockposition); + } +@@ -167,18 +187,18 @@ + map1.put(Items.YELLOW_SHULKER_BOX, CauldronInteraction::shulkerBoxInteraction); + Map map2 = CauldronInteraction.LAVA.map(); + +- map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.LAVA_BUCKET), (iblockdata1) -> { + return true; +- }, SoundEvents.BUCKET_FILL_LAVA); ++ }, SoundEvents.BUCKET_FILL_LAVA, hitDirection); // Paper - add hitDirection + }); + CauldronInteraction.addDefaultInteractions(map2); + Map map3 = CauldronInteraction.POWDER_SNOW.map(); + +- map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { ++ map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection + return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.POWDER_SNOW_BUCKET), (iblockdata1) -> { + return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3; +- }, SoundEvents.BUCKET_FILL_POWDER_SNOW); ++ }, SoundEvents.BUCKET_FILL_POWDER_SNOW, hitDirection); // Paper - add hitDirection + }); + CauldronInteraction.addDefaultInteractions(map3); + } +@@ -190,16 +210,35 @@ + } + + static InteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate fullPredicate, SoundEvent soundEvent) { ++ // Paper start - add hitDirection ++ return fillBucket(state, world, pos, player, hand, stack, output, fullPredicate, soundEvent, null); // Paper - add hitDirection ++ } ++ static InteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate fullPredicate, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) { ++ // Paper end - add hitDirection + if (!fullPredicate.test(state)) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else { + if (!world.isClientSide) { ++ // Paper start - fire PlayerBucketFillEvent ++ if (hitDirection != null) { ++ org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, output.getItem(), hand); ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; ++ } ++ // Paper end - fire PlayerBucketFillEvent ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + Item item = stack.getItem(); + + player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, output)); + player.awardStat(Stats.USE_CAULDRON); + player.awardStat(Stats.ITEM_USED.get(item)); +- world.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState()); ++ // world.setBlockAndUpdate(blockposition, Blocks.CAULDRON.defaultBlockState()); // CraftBukkit + world.playSound((Player) null, pos, soundEvent, SoundSource.BLOCKS, 1.0F, 1.0F); + world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, pos); + } +@@ -209,13 +248,33 @@ + } + + static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) { ++ // Paper start - add hitDirection ++ return emptyBucket(world, pos, player, hand, stack, state, soundEvent, null); ++ } ++ static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) { ++ // Paper end - add hitDirection + if (!world.isClientSide) { ++ // Paper start - fire PlayerBucketEmptyEvent ++ ItemStack output = new ItemStack(Items.BUCKET); ++ if (hitDirection != null) { ++ org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, hand); ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; ++ } ++ // Paper end - fire PlayerBucketEmptyEvent ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + Item item = stack.getItem(); + +- player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.BUCKET))); ++ player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, output)); // Paper + player.awardStat(Stats.FILL_CAULDRON); + player.awardStat(Stats.ITEM_USED.get(item)); +- world.setBlockAndUpdate(pos, state); ++ // world.setBlockAndUpdate(blockposition, iblockdata); // CraftBukkit + world.playSound((Player) null, pos, soundEvent, SoundSource.BLOCKS, 1.0F, 1.0F); + world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, pos); + } +@@ -223,65 +282,79 @@ + return InteractionResult.SUCCESS; + } + +- private static InteractionResult fillWaterInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { +- return CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY); ++ private static InteractionResult fillWaterInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection ++ return CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY, hitDirection); // Paper - add hitDirection + } + +- private static InteractionResult fillLavaInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { +- return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA)); ++ private static InteractionResult fillLavaInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection ++ return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA, hitDirection)); // Paper - add hitDirection + } + +- private static InteractionResult fillPowderSnowInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { +- return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW)); ++ private static InteractionResult fillPowderSnowInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection ++ return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection)); // Paper - add hitDirection + } + +- private static InteractionResult shulkerBoxInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { ++ private static InteractionResult shulkerBoxInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection + Block block = Block.byItem(stack.getItem()); + + if (!(block instanceof ShulkerBoxBlock)) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else { + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.SHULKER_WASH)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + ItemStack itemstack1 = stack.transmuteCopy(Blocks.SHULKER_BOX, 1); + + player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemstack1, false)); + player.awardStat(Stats.CLEAN_SHULKER_BOX); +- LayeredCauldronBlock.lowerFillLevel(state, world, pos); ++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit + } +- + return InteractionResult.SUCCESS; + } + } + +- private static InteractionResult bannerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { ++ private static InteractionResult bannerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection + BannerPatternLayers bannerpatternlayers = (BannerPatternLayers) stack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY); + + if (bannerpatternlayers.layers().isEmpty()) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else { + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.BANNER_WASH)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + ItemStack itemstack1 = stack.copyWithCount(1); + + itemstack1.set(DataComponents.BANNER_PATTERNS, bannerpatternlayers.removeLast()); + player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemstack1, false)); + player.awardStat(Stats.CLEAN_BANNER); +- LayeredCauldronBlock.lowerFillLevel(state, world, pos); ++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit + } + + return InteractionResult.SUCCESS; + } + } + +- private static InteractionResult dyedItemIteration(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) { ++ private static InteractionResult dyedItemIteration(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection + if (!stack.is(ItemTags.DYEABLE)) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else if (!stack.has(DataComponents.DYED_COLOR)) { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } else { + if (!world.isClientSide) { ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.ARMOR_WASH)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + stack.remove(DataComponents.DYED_COLOR); + player.awardStat(Stats.CLEAN_ARMOR); +- LayeredCauldronBlock.lowerFillLevel(state, world, pos); ++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit + } + + return InteractionResult.SUCCESS; +@@ -294,8 +367,10 @@ + return fluid.is(FluidTags.WATER); + } + ++ // CraftBukkit start - decompile errors ++ /* + static { +- Function function = CauldronInteraction.InteractionMap::name; ++ Function function = CauldronInteraction.a::name; + Map map = CauldronInteraction.INTERACTIONS; + + Objects.requireNonNull(map); +@@ -305,6 +380,8 @@ + LAVA = newInteractionMap("lava"); + POWDER_SNOW = newInteractionMap("powder_snow"); + } ++ */ ++ // CraftBukkit end + + public static record InteractionMap(String name, Map map) { + diff --git a/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch new file mode 100644 index 0000000000..53846b237e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch @@ -0,0 +1,70 @@ +--- a/net/minecraft/core/component/DataComponentPatch.java ++++ b/net/minecraft/core/component/DataComponentPatch.java +@@ -61,7 +61,7 @@ + } + } + +- return reference2objectmap; ++ return (Reference2ObjectMap) reference2objectmap; // CraftBukkit - decompile error + }); + public static final StreamCodec STREAM_CODEC = new StreamCodec() { + public DataComponentPatch decode(RegistryFriendlyByteBuf registryfriendlybytebuf) { +@@ -144,7 +144,13 @@ + } + + private static void encodeComponent(RegistryFriendlyByteBuf buf, DataComponentType type, Object value) { +- type.streamCodec().encode(buf, value); ++ // Paper start - codec errors of random anonymous classes are useless ++ try { ++ type.streamCodec().encode(buf, (T) value); // CraftBukkit - decompile error ++ } catch (final Exception e) { ++ throw new RuntimeException("Error encoding component " + type, e); ++ } ++ // Paper end - codec errors of random anonymous classes are useless + } + }; + private static final String REMOVED_PREFIX = "!"; +@@ -270,7 +276,43 @@ + private final Reference2ObjectMap, Optional> map = new Reference2ObjectArrayMap(); + + Builder() {} ++ ++ // CraftBukkit start ++ public void copy(DataComponentPatch orig) { ++ this.map.putAll(orig.map); ++ } ++ ++ public void clear(DataComponentType type) { ++ this.map.remove(type); ++ } ++ ++ public boolean isSet(DataComponentType type) { ++ return this.map.containsKey(type); ++ } ++ ++ public boolean isEmpty() { ++ return this.map.isEmpty(); ++ } + ++ @Override ++ public boolean equals(Object object) { ++ if (this == object) { ++ return true; ++ } ++ ++ if (object instanceof DataComponentPatch.Builder patch) { ++ return this.map.equals(patch.map); ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ // CraftBukkit end ++ + public DataComponentPatch.Builder set(DataComponentType type, T value) { + this.map.put(type, Optional.of(value)); + return this; diff --git a/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch new file mode 100644 index 0000000000..eb470da08e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/core/component/DataComponents.java ++++ b/net/minecraft/core/component/DataComponents.java +@@ -180,10 +180,10 @@ + "map_post_processing", builder -> builder.networkSynchronized(MapPostProcessing.STREAM_CODEC) + ); + public static final DataComponentType CHARGED_PROJECTILES = register( +- "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(ChargedProjectiles.STREAM_CODEC).cacheEncoding() ++ "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CHARGED_PROJECTILES).cacheEncoding() // Paper - sanitize charged projectiles + ); + public static final DataComponentType BUNDLE_CONTENTS = register( +- "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(BundleContents.STREAM_CODEC).cacheEncoding() ++ "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.BUNDLE_CONTENTS).cacheEncoding() // Paper - sanitize bundle contents + ); + public static final DataComponentType POTION_CONTENTS = register( + "potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding() +@@ -250,7 +250,7 @@ + "pot_decorations", builder -> builder.persistent(PotDecorations.CODEC).networkSynchronized(PotDecorations.STREAM_CODEC).cacheEncoding() + ); + public static final DataComponentType CONTAINER = register( +- "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(ItemContainerContents.STREAM_CODEC).cacheEncoding() ++ "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CONTAINER).cacheEncoding() // Paper - sanitize container contents + ); + public static final DataComponentType BLOCK_STATE = register( + "block_state", builder -> builder.persistent(BlockItemStateProperties.CODEC).networkSynchronized(BlockItemStateProperties.STREAM_CODEC).cacheEncoding() diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..a97acf2799 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -11,6 +11,11 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.DispenserBlock; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end + + public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + +@@ -43,14 +48,40 @@ + d4 = 0.0D; + } + ++ // CraftBukkit start ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink at end and single item in event ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end + AbstractBoat abstractboat = (AbstractBoat) this.type.create(worldserver, EntitySpawnReason.DISPENSER); + + if (abstractboat != null) { +- abstractboat.setInitialPos(d1, d2 + d4, d3); ++ abstractboat.setInitialPos(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); // CraftBukkit + EntityType.createDefaultStackConfig(worldserver, stack, (Player) null).accept(abstractboat); + abstractboat.setYRot(enumdirection.toYRot()); +- worldserver.addFreshEntity(abstractboat); +- stack.shrink(1); ++ if (worldserver.addFreshEntity(abstractboat) && shrink) stack.shrink(1); // Paper - if entity add was successful and supposed to shrink + } + + return stack; diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..9a3359fc85 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch @@ -0,0 +1,126 @@ +--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -6,47 +6,114 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.DispenserBlock; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftVector; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end + + public class DefaultDispenseItemBehavior implements DispenseItemBehavior { ++ private Direction enumdirection; // Paper - cache facing direction + + private static final int DEFAULT_ACCURACY = 6; + ++ // CraftBukkit start ++ private boolean dropper; ++ ++ public DefaultDispenseItemBehavior(boolean dropper) { ++ this.dropper = dropper; ++ } ++ // CraftBukkit end ++ + public DefaultDispenseItemBehavior() {} + + @Override + public final ItemStack dispense(BlockSource pointer, ItemStack stack) { ++ enumdirection = pointer.state().getValue(DispenserBlock.FACING); // Paper - cache facing direction + ItemStack itemstack1 = this.execute(pointer, stack); + + this.playSound(pointer); +- this.playAnimation(pointer, (Direction) pointer.state().getValue(DispenserBlock.FACING)); ++ this.playAnimation(pointer, enumdirection); // Paper - cache facing direction + return itemstack1; + } + + protected ItemStack execute(BlockSource pointer, ItemStack stack) { +- Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); ++ // Paper - cached enum direction + Position iposition = DispenserBlock.getDispensePosition(pointer); + ItemStack itemstack1 = stack.split(1); + +- DefaultDispenseItemBehavior.spawnItem(pointer.level(), itemstack1, 6, enumdirection, iposition); ++ // CraftBukkit start ++ if (!DefaultDispenseItemBehavior.spawnItem(pointer.level(), itemstack1, 6, enumdirection, pointer, this.dropper)) { ++ stack.grow(1); ++ } ++ // CraftBukkit end + return stack; + } + + public static void spawnItem(Level world, ItemStack stack, int speed, Direction side, Position pos) { +- double d0 = pos.x(); +- double d1 = pos.y(); +- double d2 = pos.z(); ++ // CraftBukkit start ++ ItemEntity entityitem = DefaultDispenseItemBehavior.prepareItem(world, stack, speed, side, pos); ++ world.addFreshEntity(entityitem); ++ } + +- if (side.getAxis() == Direction.Axis.Y) { ++ private static ItemEntity prepareItem(Level world, ItemStack itemstack, int i, Direction enumdirection, Position iposition) { ++ // CraftBukkit end ++ double d0 = iposition.x(); ++ double d1 = iposition.y(); ++ double d2 = iposition.z(); ++ ++ if (enumdirection.getAxis() == Direction.Axis.Y) { + d1 -= 0.125D; + } else { + d1 -= 0.15625D; + } + +- ItemEntity entityitem = new ItemEntity(world, d0, d1, d2, stack); ++ ItemEntity entityitem = new ItemEntity(world, d0, d1, d2, itemstack); + double d3 = world.random.nextDouble() * 0.1D + 0.2D; + +- entityitem.setDeltaMovement(world.random.triangle((double) side.getStepX() * d3, 0.0172275D * (double) speed), world.random.triangle(0.2D, 0.0172275D * (double) speed), world.random.triangle((double) side.getStepZ() * d3, 0.0172275D * (double) speed)); ++ entityitem.setDeltaMovement(world.random.triangle((double) enumdirection.getStepX() * d3, 0.0172275D * (double) i), world.random.triangle(0.2D, 0.0172275D * (double) i), world.random.triangle((double) enumdirection.getStepZ() * d3, 0.0172275D * (double) i)); ++ // CraftBukkit start ++ return entityitem; ++ } ++ ++ // CraftBukkit - void -> boolean return, IPosition -> ISourceBlock last argument, dropper ++ public static boolean spawnItem(Level world, ItemStack itemstack, int i, Direction enumdirection, BlockSource sourceblock, boolean dropper) { ++ if (itemstack.isEmpty()) return true; ++ Position iposition = DispenserBlock.getDispensePosition(sourceblock); ++ ItemEntity entityitem = DefaultDispenseItemBehavior.prepareItem(world, itemstack, i, enumdirection, iposition); ++ ++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), CraftVector.toBukkit(entityitem.getDeltaMovement())); ++ if (!DispenserBlock.eventFired) { ++ world.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ entityitem.setItem(CraftItemStack.asNMSCopy(event.getItem())); ++ entityitem.setDeltaMovement(CraftVector.toNMS(event.getVelocity())); ++ ++ if (!dropper && !event.getItem().getType().equals(craftItem.getType())) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(sourceblock, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior.getClass() != DefaultDispenseItemBehavior.class) { ++ idispensebehavior.dispense(sourceblock, eventStack); ++ } else { ++ world.addFreshEntity(entityitem); ++ } ++ return false; ++ } ++ + world.addFreshEntity(entityitem); ++ ++ return true; ++ // CraftBukkit end + } + + protected void playSound(BlockSource pointer) { diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch new file mode 100644 index 0000000000..824b11e0d1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch @@ -0,0 +1,651 @@ +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -28,6 +28,7 @@ + import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.BoneMealItem; ++import net.minecraft.world.item.BucketItem; + import net.minecraft.world.item.DispensibleContainerItem; + import net.minecraft.world.item.DyeColor; + import net.minecraft.world.item.HoneycombItem; +@@ -47,7 +48,9 @@ + import net.minecraft.world.level.block.CandleCakeBlock; + import net.minecraft.world.level.block.CarvedPumpkinBlock; + import net.minecraft.world.level.block.DispenserBlock; ++import net.minecraft.world.level.block.LiquidBlockContainer; + import net.minecraft.world.level.block.RespawnAnchorBlock; ++import net.minecraft.world.level.block.SaplingBlock; + import net.minecraft.world.level.block.ShulkerBoxBlock; + import net.minecraft.world.level.block.SkullBlock; + import net.minecraft.world.level.block.TntBlock; +@@ -62,6 +65,17 @@ + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import org.slf4j.Logger; ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.craftbukkit.util.DummyGeneratorAccess; ++import org.bukkit.event.block.BlockDispenseArmorEvent; ++import org.bukkit.event.block.BlockDispenseEvent; ++import org.bukkit.event.block.BlockFertilizeEvent; ++import org.bukkit.event.world.StructureGrowEvent; ++// CraftBukkit end + + public interface DispenseItemBehavior { + +@@ -90,14 +104,47 @@ + Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); + EntityType entitytypes = ((SpawnEggItem) stack.getItem()).getType(pointer.level().registryAccess(), stack); + ++ // CraftBukkit start ++ ServerLevel worldserver = pointer.level(); ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ // Paper start - track changed items in the dispense event ++ itemstack1 = CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified ++ entitytypes = ((SpawnEggItem) itemstack1.getItem()).getType(worldserver.registryAccess(), itemstack1); ++ // Paper end - track changed item from dispense event ++ } ++ + try { +- entitytypes.spawn(pointer.level(), stack, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false); ++ entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false); // Paper - track changed item in dispense event + } catch (Exception exception) { +- null.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); ++ DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); // CraftBukkit - decompile error + return ItemStack.EMPTY; + } + +- stack.shrink(1); ++ if (shrink) stack.shrink(1); // Paper - actually handle here ++ // CraftBukkit end + pointer.level().gameEvent((Entity) null, (Holder) GameEvent.ENTITY_PLACE, pointer.pos()); + return stack; + } +@@ -116,13 +163,43 @@ + Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); + BlockPos blockposition = pointer.pos().relative(enumdirection); + ServerLevel worldserver = pointer.level(); ++ ++ // CraftBukkit start ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ ++ final ItemStack newStack = CraftItemStack.unwrap(event.getItem()); // Paper - use event itemstack (unwrap is fine here because the stack won't be modified) + Consumer consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> { + entityarmorstand.setYRot(enumdirection.toYRot()); +- }, worldserver, stack, (Player) null); ++ }, worldserver, newStack, (Player) null); // Paper - track changed items in the dispense event + ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, consumer, blockposition, EntitySpawnReason.DISPENSER, false, false); + + if (entityarmorstand != null) { +- stack.shrink(1); ++ if (shrink) stack.shrink(1); // Paper - actually handle here + } + + return stack; +@@ -141,7 +218,36 @@ + }); + + if (!list.isEmpty()) { +- ((Saddleable) list.get(0)).equipSaddle(stack.split(1), SoundSource.BLOCKS); ++ // CraftBukkit start ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event ++ ServerLevel world = pointer.level(); ++ org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity()); ++ if (!DispenserBlock.eventFired) { ++ world.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ((Saddleable) list.get(0)).equipSaddle(CraftItemStack.asNMSCopy(event.getItem()), SoundSource.BLOCKS); // Paper - track changed items in dispense event ++ // CraftBukkit end ++ if (shrink) stack.shrink(1); // Paper - actually handle here + this.setSuccess(true); + return stack; + } else { +@@ -166,9 +272,38 @@ + } + + entityhorsechestedabstract = (AbstractChestedHorse) iterator1.next(); +- } while (!entityhorsechestedabstract.isTamed() || !entityhorsechestedabstract.getSlot(499).set(stack)); ++ // CraftBukkit start ++ } while (!entityhorsechestedabstract.isTamed()); ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below ++ ServerLevel world = pointer.level(); ++ org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); + +- stack.shrink(1); ++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity()); ++ if (!DispenserBlock.eventFired) { ++ world.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below (this was actually missing and should be here, added it commented out to be consistent) ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ entityhorsechestedabstract.getSlot(499).set(CraftItemStack.asNMSCopy(event.getItem())); ++ // CraftBukkit end ++ ++ if (shrink) stack.shrink(1); // Paper - actually handle here + this.setSuccess(true); + return stack; + } +@@ -202,8 +337,50 @@ + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + ServerLevel worldserver = pointer.level(); + ++ // CraftBukkit start ++ int x = blockposition.getX(); ++ int y = blockposition.getY(); ++ int z = blockposition.getZ(); ++ BlockState iblockdata = worldserver.getBlockState(blockposition); ++ ItemStack dispensedItem = stack; // Paper - track changed item from the dispense event ++ // Paper start - correctly check if the bucket place will succeed ++ /* Taken from SolidBucketItem#emptyContents */ ++ boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir(); ++ /* Taken from BucketItem#emptyContents */ ++ boolean willEmptyBucketItem = dispensiblecontaineritem instanceof final BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, worldserver, blockposition, iblockdata, bucketItem.content))); ++ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) { ++ // Paper end - correctly check if the bucket place will succeed ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ // Paper start - track changed item from dispense event ++ dispensedItem = CraftItemStack.unwrap(event.getItem()); // unwrap is safe here as the stack isn't mutated ++ dispensiblecontaineritem = (DispensibleContainerItem) dispensedItem.getItem(); ++ // Paper end - track changed item from dispense event ++ } ++ // CraftBukkit end ++ + if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) { +- dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, stack, blockposition); ++ dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition); // Paper - track changed item from dispense event + return this.consumeWithRemainder(pointer, stack, new ItemStack(Items.BUCKET)); + } else { + return this.defaultDispenseItemBehavior.dispense(pointer, stack); +@@ -229,7 +406,7 @@ + Block block = iblockdata.getBlock(); + + if (block instanceof BucketPickup ifluidsource) { +- ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, worldserver, blockposition, iblockdata); ++ ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); // CraftBukkit + + if (itemstack1.isEmpty()) { + return super.execute(pointer, stack); +@@ -237,6 +414,32 @@ + worldserver.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, blockposition); + Item item = itemstack1.getItem(); + ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ itemstack1 = ifluidsource.pickupBlock((Player) null, worldserver, blockposition, iblockdata); // From above ++ // CraftBukkit end ++ + return this.consumeWithRemainder(pointer, stack, new ItemStack(item)); + } + } else { +@@ -249,16 +452,44 @@ + protected ItemStack execute(BlockSource pointer, ItemStack stack) { + ServerLevel worldserver = pointer.level(); + ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + this.setSuccess(true); + Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); + BlockPos blockposition = pointer.pos().relative(enumdirection); + BlockState iblockdata = worldserver.getBlockState(blockposition); + + if (BaseFireBlock.canBePlacedAt(worldserver, blockposition, enumdirection)) { +- worldserver.setBlockAndUpdate(blockposition, BaseFireBlock.getState(worldserver, blockposition)); +- worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition); ++ // CraftBukkit start - Ignition by dispensing flint and steel ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(worldserver, blockposition, pointer.pos()).isCancelled()) { ++ worldserver.setBlockAndUpdate(blockposition, BaseFireBlock.getState(worldserver, blockposition)); ++ worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition); ++ } ++ // CraftBukkit end + } else if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) { +- if (iblockdata.getBlock() instanceof TntBlock) { ++ if (iblockdata.getBlock() instanceof TntBlock && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(worldserver, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, pointer.pos())) { // CraftBukkit - TNTPrimeEvent + TntBlock.explode(worldserver, blockposition); + worldserver.removeBlock(blockposition, false); + } else { +@@ -283,13 +514,64 @@ + this.setSuccess(true); + ServerLevel worldserver = pointer.level(); + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); ++ // CraftBukkit start ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event + ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ worldserver.captureTreeGeneration = true; ++ // CraftBukkit end ++ + if (!BoneMealItem.growCrop(stack, worldserver, blockposition) && !BoneMealItem.growWaterPlant(stack, worldserver, blockposition, (Direction) null)) { + this.setSuccess(false); + } else if (!worldserver.isClientSide) { + worldserver.levelEvent(1505, blockposition, 15); + } ++ // CraftBukkit start ++ worldserver.captureTreeGeneration = false; ++ if (worldserver.capturedBlockStates.size() > 0) { ++ TreeType treeType = SaplingBlock.treeType; ++ SaplingBlock.treeType = null; ++ Location location = CraftLocation.toBukkit(blockposition, worldserver.getWorld()); ++ List blocks = new java.util.ArrayList<>(worldserver.capturedBlockStates.values()); ++ worldserver.capturedBlockStates.clear(); ++ StructureGrowEvent structureEvent = null; ++ if (treeType != null) { ++ structureEvent = new StructureGrowEvent(location, treeType, false, null, blocks); ++ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); ++ } + ++ BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(location.getBlock(), null, blocks); ++ fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); ++ org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); ++ ++ if (!fertilizeEvent.isCancelled()) { ++ for (org.bukkit.block.BlockState blockstate : blocks) { ++ blockstate.update(true); ++ worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed ++ } ++ } ++ } ++ // CraftBukkit end ++ + return stack; + } + }); +@@ -298,12 +580,42 @@ + protected ItemStack execute(BlockSource pointer, ItemStack stack) { + ServerLevel worldserver = pointer.level(); + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); +- PrimedTnt entitytntprimed = new PrimedTnt(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (LivingEntity) null); ++ // CraftBukkit start ++ // EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null); + ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink at end and single item in event ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ PrimedTnt entitytntprimed = new PrimedTnt(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (LivingEntity) null); ++ // CraftBukkit end ++ + worldserver.addFreshEntity(entitytntprimed); + worldserver.playSound((Player) null, entitytntprimed.getX(), entitytntprimed.getY(), entitytntprimed.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F); + worldserver.gameEvent((Entity) null, (Holder) GameEvent.ENTITY_PLACE, blockposition); +- stack.shrink(1); ++ if (shrink) stack.shrink(1); // Paper - actually handle here + return stack; + } + }); +@@ -313,7 +625,31 @@ + ServerLevel worldserver = pointer.level(); + Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); + BlockPos blockposition = pointer.pos().relative(enumdirection); ++ ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + if (worldserver.isEmptyBlock(blockposition) && WitherSkullBlock.canSpawnMob(worldserver, blockposition, stack)) { + worldserver.setBlock(blockposition, (BlockState) Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(enumdirection)), 3); + worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition); +@@ -326,7 +662,7 @@ + stack.shrink(1); + this.setSuccess(true); + } else { +- this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack)); ++ this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError + } + + return stack; +@@ -339,6 +675,30 @@ + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + CarvedPumpkinBlock blockpumpkincarved = (CarvedPumpkinBlock) Blocks.CARVED_PUMPKIN; + ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + if (worldserver.isEmptyBlock(blockposition) && blockpumpkincarved.canSpawnGolem(worldserver, blockposition)) { + if (!worldserver.isClientSide) { + worldserver.setBlock(blockposition, blockpumpkincarved.defaultBlockState(), 3); +@@ -348,7 +708,7 @@ + stack.shrink(1); + this.setSuccess(true); + } else { +- this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack)); ++ this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError + } + + return stack; +@@ -377,6 +737,30 @@ + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + BlockState iblockdata = worldserver.getBlockState(blockposition); + ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - only single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + if (iblockdata.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> { + return blockbase_blockdata.hasProperty(BeehiveBlock.HONEY_LEVEL) && blockbase_blockdata.getBlock() instanceof BeehiveBlock; + }) && (Integer) iblockdata.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) { +@@ -402,6 +786,13 @@ + this.setSuccess(true); + if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { + if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata); + stack.shrink(1); + } else { +@@ -426,6 +817,31 @@ + this.setSuccess(false); + return stack; + } else { ++ // CraftBukkit start ++ ItemStack itemstack1 = stack; ++ ServerLevel world = pointer.level(); ++ org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); // Paper - ignore stack size on damageable items ++ ++ BlockDispenseEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity()); ++ if (!DispenserBlock.eventFired) { ++ world.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end + Iterator iterator1 = list.iterator(); + + Armadillo armadillo; +@@ -454,6 +870,13 @@ + Optional optional = HoneycombItem.getWaxed(iblockdata); + + if (optional.isPresent()) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get()); + worldserver.levelEvent(3003, blockposition, 0); + stack.shrink(1); +@@ -481,6 +904,12 @@ + if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) { + return this.defaultDispenseItemBehavior.dispense(pointer, stack); + } else { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this); ++ if (result != null) { ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + if (!worldserver.isClientSide) { + for (int k = 0; k < 5; ++k) { + worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D); diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..18eb499fba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch @@ -0,0 +1,82 @@ +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -7,8 +7,13 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; + import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.DispenserBlock; + import net.minecraft.world.phys.AABB; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseArmorEvent; ++// CraftBukkit end + + public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + +@@ -18,10 +23,15 @@ + + @Override + protected ItemStack execute(BlockSource pointer, ItemStack stack) { +- return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack) ? stack : super.execute(pointer, stack); ++ return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this) ? stack : super.execute(pointer, stack); // Paper - fix possible StackOverflowError + } + +- public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack) { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper ++ public static boolean dispenseEquipment(BlockSource pointer, ItemStack armor) { ++ // Paper start ++ return dispenseEquipment(pointer, armor, null); ++ } ++ public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack, @javax.annotation.Nullable DispenseItemBehavior currentBehavior) { + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + List list = pointer.level().getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), (entityliving) -> { + return entityliving.canEquipWithDispenser(stack); +@@ -32,9 +42,37 @@ + } else { + LivingEntity entityliving = (LivingEntity) list.getFirst(); + EquipmentSlot enumitemslot = entityliving.getEquipmentSlotForItem(stack); +- ItemStack itemstack1 = stack.split(1); ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event + +- entityliving.setItemSlot(enumitemslot, itemstack1); ++ // CraftBukkit start ++ Level world = pointer.level(); ++ org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityliving.getBukkitEntity()); ++ if (!DispenserBlock.eventFired) { ++ world.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return false; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && (currentBehavior == null || idispensebehavior != currentBehavior)) { // Paper - fix possible StackOverflowError ++ idispensebehavior.dispense(pointer, eventStack); ++ return true; ++ } ++ } ++ ++ entityliving.setItemSlot(enumitemslot, CraftItemStack.asNMSCopy(event.getItem())); ++ // CraftBukkit end + if (entityliving instanceof Mob) { + Mob entityinsentient = (Mob) entityliving; + +@@ -42,6 +80,7 @@ + entityinsentient.setPersistenceRequired(); + } + ++ if (shrink) stack.shrink(1); // Paper - shrink here + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..ea22a6dd42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -15,6 +15,11 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end + + public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + +@@ -62,11 +67,40 @@ + } + + Vec3 vec3d1 = new Vec3(d0, d1 + d3, d2); +- AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, vec3d1.x, vec3d1.y, vec3d1.z, this.entityType, EntitySpawnReason.DISPENSER, stack, (Player) null); ++ // CraftBukkit start ++ // EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.createMinecart(worldserver, vec3d1.x, vec3d1.y, vec3d1.z, this.entityType, EntitySpawnReason.DISPENSER, itemstack, (EntityHuman) null); ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event ++ org.bukkit.block.Block block2 = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); + ++ BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec3d1.x, vec3d1.y, vec3d1.z)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); ++ AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.entityType, EntitySpawnReason.DISPENSER, itemstack1, (Player) null); ++ + if (entityminecartabstract != null) { +- worldserver.addFreshEntity(entityminecartabstract); +- stack.shrink(1); ++ if (worldserver.addFreshEntity(entityminecartabstract) && shrink) stack.shrink(1); // Paper - if entity add was successful and supposed to shrink ++ // CraftBukkit end + } + + return stack; diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch new file mode 100644 index 0000000000..2061a38275 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -8,6 +8,11 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.ProjectileItem; + import net.minecraft.world.level.block.DispenserBlock; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end + + public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + +@@ -31,8 +36,41 @@ + Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); + Position iposition = this.dispenseConfig.positionFunction().getDispensePosition(pointer, enumdirection); + +- Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, stack, enumdirection), worldserver, stack, (double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); +- stack.shrink(1); ++ // CraftBukkit start ++ // IProjectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, itemstack, enumdirection), worldserver, itemstack, (double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // CraftBukkit - call when finish the BlockDispenseEvent ++ ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event ++ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ())); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // stack.grow(1); // Paper - shrink below ++ return stack; ++ } ++ ++ boolean shrink = true; // Paper ++ if (!event.getItem().equals(craftItem)) { ++ shrink = false; // Paper - shrink below ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ ++ // SPIGOT-7923: Avoid create projectiles with empty item ++ if (!itemstack1.isEmpty()) { ++ Projectile iprojectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, CraftItemStack.unwrap(event.getItem()), enumdirection), worldserver, itemstack1, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies ++ iprojectile.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(pointer.blockEntity()); ++ } ++ if (shrink) stack.shrink(1); // Paper - actually handle here ++ // CraftBukkit end + return stack; + } + diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..bc579690dd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -22,6 +22,12 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.AABB; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end + + public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + +@@ -30,11 +36,34 @@ + @Override + protected ItemStack execute(BlockSource pointer, ItemStack stack) { + ServerLevel worldserver = pointer.level(); ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items + ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); ++ if (!DispenserBlock.eventFired) { ++ worldserver.getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + if (!worldserver.isClientSide()) { + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + +- this.setSuccess(ShearsDispenseItemBehavior.tryShearBeehive(worldserver, blockposition) || ShearsDispenseItemBehavior.tryShearLivingEntity(worldserver, blockposition, stack)); ++ this.setSuccess(ShearsDispenseItemBehavior.tryShearBeehive(worldserver, blockposition) || ShearsDispenseItemBehavior.tryShearLivingEntity(worldserver, blockposition, stack, bukkitBlock, craftItem)); // CraftBukkit + if (this.isSuccess()) { + stack.hurtAndBreak(1, worldserver, (ServerPlayer) null, (item) -> { + }); +@@ -64,8 +93,8 @@ + return false; + } + +- private static boolean tryShearLivingEntity(ServerLevel world, BlockPos pos, ItemStack shears) { +- List list = world.getEntitiesOfClass(LivingEntity.class, new AABB(pos), EntitySelector.NO_SPECTATORS); ++ private static boolean tryShearLivingEntity(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, org.bukkit.block.Block bukkitBlock, CraftItemStack craftItem) { // CraftBukkit - add args ++ List list = worldserver.getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), EntitySelector.NO_SPECTATORS); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +@@ -73,8 +102,16 @@ + + if (entityliving instanceof Shearable ishearable) { + if (ishearable.readyForShearing()) { +- ishearable.shear(world, SoundSource.BLOCKS, shears); +- world.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, pos); ++ // CraftBukkit start ++ // Paper start - Add drops to shear events ++ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops(worldserver, itemstack)); ++ if (event.isCancelled()) { ++ // Paper end - Add drops to shear events ++ continue; ++ } ++ // CraftBukkit end ++ ishearable.shear(worldserver, SoundSource.BLOCKS, itemstack, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events ++ worldserver.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, blockposition); + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch new file mode 100644 index 0000000000..bfa73fef42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch @@ -0,0 +1,54 @@ +--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -10,6 +10,12 @@ + import net.minecraft.world.level.block.DispenserBlock; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseEvent; ++// CraftBukkit end ++ + public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -26,8 +32,37 @@ + BlockPos blockposition = pointer.pos().relative(enumdirection); + Direction enumdirection1 = pointer.level().isEmptyBlock(blockposition.below()) ? enumdirection : Direction.UP; + ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event ++ ++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); ++ if (!DispenserBlock.eventFired) { ++ pointer.level().getCraftServer().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()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { ++ idispensebehavior.dispense(pointer, eventStack); ++ return stack; ++ } ++ } ++ // CraftBukkit end ++ + try { +- this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, stack, enumdirection1)).consumesAction()); ++ // Paper start - track changed items in the dispense event ++ this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, CraftItemStack.asNMSCopy(event.getItem()), enumdirection1)).consumesAction()); ++ if (this.isSuccess()) { ++ stack.shrink(1); // vanilla shrink is in the place function above, manually handle it here ++ } ++ // Paper end - track changed items in the dispense event + } catch (Exception exception) { + ShulkerBoxDispenseBehavior.LOGGER.error("Error trying to place shulker box at {}", blockposition, exception); + } diff --git a/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch new file mode 100644 index 0000000000..d363add98b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/net/minecraft/core/registries/BuiltInRegistries.java +@@ -296,6 +296,17 @@ + public static final Registry> SLOT_DISPLAY = registerSimple(Registries.SLOT_DISPLAY, SlotDisplays::bootstrap); + public static final Registry RECIPE_BOOK_CATEGORY = registerSimple(Registries.RECIPE_BOOK_CATEGORY, RecipeBookCategories::bootstrap); + public static final Registry> REGISTRY = WRITABLE_REGISTRY; ++ // Paper start - add built-in registry conversions ++ public static final io.papermc.paper.registry.data.util.Conversions BUILT_IN_CONVERSIONS = new io.papermc.paper.registry.data.util.Conversions(new net.minecraft.resources.RegistryOps.RegistryInfoLookup() { ++ @Override ++ public java.util.Optional> lookup(final ResourceKey> registryRef) { ++ final Registry registry = net.minecraft.server.RegistryLayer.STATIC_ACCESS.lookupOrThrow(registryRef); ++ return java.util.Optional.of( ++ new net.minecraft.resources.RegistryOps.RegistryInfo<>(registry, registry, Lifecycle.experimental()) ++ ); ++ } ++ }); ++ // Paper end - add built-in registry conversions + + private static Registry registerSimple(ResourceKey> key, BuiltInRegistries.RegistryBootstrap initializer) { + return internalRegister(key, new MappedRegistry<>(key, Lifecycle.stable(), false), initializer); +@@ -323,14 +334,22 @@ + ResourceKey> key, R registry, BuiltInRegistries.RegistryBootstrap initializer + ) { + Bootstrap.checkBootstrapCalled(() -> "registry " + key.location()); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry + ResourceLocation resourceLocation = key.location(); + LOADERS.put(resourceLocation, () -> initializer.run(registry)); +- WRITABLE_REGISTRY.register((ResourceKey>)key, registry, RegistrationInfo.BUILT_IN); ++ WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix + return registry; + } + + public static void bootStrap() { ++ // Paper start ++ bootStrap(() -> {}); ++ } ++ public static void bootStrap(Runnable runnable) { ++ // Paper end ++ REGISTRY.freeze(); // Paper - freeze main registry early + createContents(); ++ runnable.run(); // Paper + freeze(); + validate(REGISTRY); + } +@@ -348,6 +367,7 @@ + + for (Registry registry : REGISTRY) { + bindBootstrappedTagsToEmpty(registry); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), BUILT_IN_CONVERSIONS); // Paper + registry.freeze(); + } + } diff --git a/paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch b/paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch new file mode 100644 index 0000000000..02e103aaf6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/data/loot/packs/VanillaChestLoot.java ++++ b/net/minecraft/data/loot/packs/VanillaChestLoot.java +@@ -946,7 +946,6 @@ + .add( + LootItem.lootTableItem(Items.COMPASS) + .apply(SetItemCountFunction.setCount(ConstantValue.exactly(1.0F))) +- .apply(SetItemDamageFunction.setDamage(UniformGenerator.between(0.15F, 0.8F))) + .setWeight(1) + ) + .add(LootItem.lootTableItem(Items.BUCKET).apply(SetItemCountFunction.setCount(UniformGenerator.between(1.0F, 2.0F))).setWeight(1)) diff --git a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch new file mode 100644 index 0000000000..d7c707925b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/nbt/ByteArrayTag.java ++++ b/net/minecraft/nbt/ByteArrayTag.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.nbt; + + import java.io.DataInput; +@@ -24,6 +25,7 @@ + private static byte[] readAccounted(DataInput input, NbtAccounter tracker) throws IOException { + tracker.accountBytes(24L); + int i = input.readInt(); ++ com.google.common.base.Preconditions.checkArgument( i < 1 << 24); // Spigot + + tracker.accountBytes(1L, (long) i); + byte[] abyte = new byte[i]; diff --git a/paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch new file mode 100644 index 0000000000..1cb8cb2d7b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/nbt/CompoundTag.java ++++ b/net/minecraft/nbt/CompoundTag.java +@@ -49,7 +49,7 @@ + + private static CompoundTag loadCompound(DataInput input, NbtAccounter tracker) throws IOException { + tracker.accountBytes(48L); +- Map map = Maps.newHashMap(); ++ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag + + byte b; + while ((b = input.readByte()) != 0) { +@@ -166,7 +166,7 @@ + } + + public CompoundTag() { +- this(Maps.newHashMap()); ++ this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - Reduce memory footprint of CompoundTag + } + + @Override +@@ -232,14 +232,34 @@ + } + + public void putUUID(String key, UUID value) { ++ // Paper start - Support old UUID format ++ if (this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ this.tags.remove(key + "Most"); ++ this.tags.remove(key + "Least"); ++ } ++ // Paper end - Support old UUID format + this.tags.put(key, NbtUtils.createUUID(value)); + } + ++ ++ /** ++ * You must use {@link #hasUUID(String)} before or else it will throw an NPE. ++ */ + public UUID getUUID(String key) { ++ // Paper start - Support old UUID format ++ if (!contains(key, 11) && this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ return new UUID(this.getLong(key + "Most"), this.getLong(key + "Least")); ++ } ++ // Paper end - Support old UUID format + return NbtUtils.loadUUID(this.get(key)); + } + + public boolean hasUUID(String key) { ++ // Paper start - Support old UUID format ++ if (this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ return true; ++ } ++ // Paper end - Support old UUID format + Tag tag = this.get(key); + return tag != null && tag.getType() == IntArrayTag.TYPE && ((IntArrayTag)tag).getAsIntArray().length == 4; + } +@@ -477,8 +497,16 @@ + + @Override + public CompoundTag copy() { +- Map map = Maps.newHashMap(Maps.transformValues(this.tags, Tag::copy)); +- return new CompoundTag(map); ++ // Paper start - Reduce memory footprint of CompoundTag ++ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.tags.size(), 0.8f); ++ java.util.Iterator> iterator = (this.tags instanceof it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) ? ((it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap)this.tags).object2ObjectEntrySet().fastIterator() : this.tags.entrySet().iterator(); ++ while (iterator.hasNext()) { ++ Map.Entry entry = iterator.next(); ++ ret.put(entry.getKey(), entry.getValue().copy()); ++ } ++ ++ return new CompoundTag(ret); ++ // Paper end - Reduce memory footprint of CompoundTag + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch new file mode 100644 index 0000000000..97872e3339 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/nbt/IntArrayTag.java ++++ b/net/minecraft/nbt/IntArrayTag.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.nbt; + + import java.io.DataInput; +@@ -24,6 +25,7 @@ + private static int[] readAccounted(DataInput input, NbtAccounter tracker) throws IOException { + tracker.accountBytes(24L); + int i = input.readInt(); ++ com.google.common.base.Preconditions.checkArgument( i < 1 << 24); // Spigot + + tracker.accountBytes(4L, (long) i); + int[] aint = new int[i]; diff --git a/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch new file mode 100644 index 0000000000..f6dc9e632c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/nbt/NbtIo.java ++++ b/net/minecraft/nbt/NbtIo.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.nbt; + + import java.io.BufferedOutputStream; +@@ -324,6 +325,12 @@ + } + + public static CompoundTag read(DataInput input, NbtAccounter tracker) throws IOException { ++ // Spigot start ++ if ( input instanceof io.netty.buffer.ByteBufInputStream ) ++ { ++ input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, tracker)); ++ } ++ // Spigot end + Tag nbtbase = NbtIo.readUnnamedTag(input, tracker); + + if (nbtbase instanceof CompoundTag) { diff --git a/paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch new file mode 100644 index 0000000000..a126b25999 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/nbt/NbtUtils.java ++++ b/net/minecraft/nbt/NbtUtils.java +@@ -149,8 +149,10 @@ + if (!nbt.contains("Name", 8)) { + return Blocks.AIR.defaultBlockState(); + } else { +- ResourceLocation resourceLocation = ResourceLocation.parse(nbt.getString("Name")); +- Optional> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)); ++ // Paper start - Validate resource location ++ ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name")); ++ Optional> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty(); ++ // Paper end - Validate resource location + if (optional.isEmpty()) { + return Blocks.AIR.defaultBlockState(); + } else { diff --git a/paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch b/paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch new file mode 100644 index 0000000000..dd6b67ed47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/nbt/TagParser.java ++++ b/net/minecraft/nbt/TagParser.java +@@ -49,6 +49,7 @@ + }, CompoundTag::toString); + public static final Codec LENIENT_CODEC = Codec.withAlternative(AS_CODEC, CompoundTag.CODEC); + private final StringReader reader; ++ private int depth; // Paper + + public static CompoundTag parseTag(String string) throws CommandSyntaxException { + return new TagParser(new StringReader(string)).readSingleStruct(); +@@ -159,6 +160,7 @@ + + public CompoundTag readStruct() throws CommandSyntaxException { + this.expect('{'); ++ this.increaseDepth(); // Paper + CompoundTag compoundTag = new CompoundTag(); + this.reader.skipWhitespace(); + +@@ -182,6 +184,7 @@ + } + + this.expect('}'); ++ this.depth--; // Paper + return compoundTag; + } + +@@ -191,6 +194,7 @@ + if (!this.reader.canRead()) { + throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); + } else { ++ this.increaseDepth(); // Paper + ListTag listTag = new ListTag(); + TagType tagType = null; + +@@ -216,6 +220,7 @@ + } + + this.expect(']'); ++ this.depth--; // Paper + return listTag; + } + } +@@ -253,11 +258,11 @@ + } + + if (typeReader == ByteTag.TYPE) { +- list.add((T)((NumericTag)tag).getAsByte()); ++ list.add((T)(Byte)((NumericTag)tag).getAsByte()); // Paper - decompile fix + } else if (typeReader == LongTag.TYPE) { +- list.add((T)((NumericTag)tag).getAsLong()); ++ list.add((T)(Long)((NumericTag)tag).getAsLong()); // Paper - decompile fix + } else { +- list.add((T)((NumericTag)tag).getAsInt()); ++ list.add((T)(Integer)((NumericTag)tag).getAsInt()); // Paper - decompile fix + } + + if (!this.hasElementSeparator()) { +@@ -288,4 +293,11 @@ + this.reader.skipWhitespace(); + this.reader.expect(c); + } ++ ++ private void increaseDepth() throws CommandSyntaxException { ++ this.depth++; ++ if (this.depth > 512) { ++ throw new io.papermc.paper.brigadier.TagParseCommandSyntaxException("NBT tag is too complex, depth > 512"); ++ } ++ } + } diff --git a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch new file mode 100644 index 0000000000..f1e414f6b1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch @@ -0,0 +1,330 @@ +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -82,13 +82,13 @@ + marker.add(Connection.PACKET_MARKER); + }); + public static final Supplier NETWORK_WORKER_GROUP = Suppliers.memoize(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier LOCAL_WORKER_GROUP = Suppliers.memoize(() -> { +- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); ++ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + private static final ProtocolInfo INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND; + private final PacketFlow receiving; +@@ -96,6 +96,11 @@ + private final Queue> pendingActions = Queues.newConcurrentLinkedQueue(); + public Channel channel; + public SocketAddress address; ++ // Spigot Start ++ public java.util.UUID spoofedUUID; ++ public com.mojang.authlib.properties.Property[] spoofedProfile; ++ public boolean preparing = true; ++ // Spigot End + @Nullable + private volatile PacketListener disconnectListener; + @Nullable +@@ -114,7 +119,42 @@ + private volatile DisconnectionDetails delayedDisconnect; + @Nullable + BandwidthDebugMonitor bandwidthDebugMonitor; ++ public String hostname = ""; // CraftBukkit - add field ++ // Paper start - NetworkClient implementation ++ public int protocolVersion; ++ public java.net.InetSocketAddress virtualHost; ++ private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing ++ // Paper end + ++ // Paper start - add utility methods ++ public final net.minecraft.server.level.ServerPlayer getPlayer() { ++ if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) { ++ return impl.player; ++ } else if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) { ++ org.bukkit.craftbukkit.entity.CraftPlayer player = impl.getCraftPlayer(); ++ return player == null ? null : player.getHandle(); ++ } ++ return null; ++ } ++ // Paper end - add utility methods ++ // Paper start - packet limiter ++ protected final Object PACKET_LIMIT_LOCK = new Object(); ++ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( ++ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9) ++ ) : null; ++ protected final java.util.Map>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); ++ ++ private boolean stopReadingPackets; ++ private void killForPacketSpam() { ++ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)); ++ }), true); ++ this.setReadOnly(); ++ this.stopReadingPackets = true; ++ } ++ // Paper end - packet limiter ++ @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address ++ + public Connection(PacketFlow side) { + this.receiving = side; + } +@@ -123,6 +163,9 @@ + super.channelActive(channelhandlercontext); + this.channel = channelhandlercontext.channel(); + this.address = this.channel.remoteAddress(); ++ // Spigot Start ++ this.preparing = false; ++ // Spigot End + if (this.delayedDisconnect != null) { + this.disconnect(this.delayedDisconnect); + } +@@ -134,6 +177,21 @@ + } + + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { ++ // Paper start - Handle large packets disconnecting client ++ if (throwable instanceof io.netty.handler.codec.EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) { ++ final Packet packet = packetTooLargeException.getPacket(); ++ if (packet.packetTooLarge(this)) { ++ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); ++ return; ++ } else if (packet.isSkippable()) { ++ Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); ++ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); ++ return; ++ } else { ++ throwable = throwable.getCause(); ++ } ++ } ++ // Paper end - Handle large packets disconnecting client + if (throwable instanceof SkipPacketException) { + Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); + } else { +@@ -141,8 +199,10 @@ + + this.handlingFault = true; + if (this.channel.isOpen()) { ++ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason + if (throwable instanceof TimeoutException) { + Connection.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason + this.disconnect((Component) Component.translatable("disconnect.timeout")); + } else { + MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + String.valueOf(throwable)); +@@ -155,9 +215,11 @@ + disconnectiondetails = new DisconnectionDetails(ichatmutablecomponent); + } + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason + if (flag) { + Connection.LOGGER.debug("Failed to sent packet", throwable); +- if (this.getSending() == PacketFlow.CLIENTBOUND) { ++ boolean doesDisconnectExist = this.packetListener.protocol() != ConnectionProtocol.STATUS && this.packetListener.protocol() != ConnectionProtocol.HANDSHAKING; // Paper ++ if (this.getSending() == PacketFlow.CLIENTBOUND && doesDisconnectExist) { // Paper + Packet packet = this.sendLoginDisconnect ? new ClientboundLoginDisconnectPacket(ichatmutablecomponent) : new ClientboundDisconnectPacket(ichatmutablecomponent); + + this.send((Packet) packet, PacketSendListener.thenRun(() -> { +@@ -176,6 +238,7 @@ + + } + } ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper + } + + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { +@@ -185,11 +248,61 @@ + if (packetlistener == null) { + throw new IllegalStateException("Received a packet before the packet listener was initialized"); + } else { ++ // Paper start - packet limiter ++ if (this.stopReadingPackets) { ++ return; ++ } ++ if (this.allPacketCounts != null || ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) { ++ long time = System.nanoTime(); ++ synchronized (PACKET_LIMIT_LOCK) { ++ if (this.allPacketCounts != null) { ++ this.allPacketCounts.updateAndAdd(1, time); ++ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) { ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ ++ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { ++ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit = ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check); ++ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) { ++ continue; ++ } ++ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { ++ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9)); ++ }); ++ counter.updateAndAdd(1, time); ++ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) { ++ switch (packetSpecificLimit.action()) { ++ case DROP: ++ return; ++ case KICK: ++ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName()); ++ ++ String playerName; ++ if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) { ++ playerName = impl.getOwner().getName(); ++ } else { ++ playerName = this.getLoggableAddress(net.minecraft.server.MinecraftServer.getServer().logIPs()); ++ } ++ ++ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1)); ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ } ++ } ++ } ++ // Paper end - packet limiter + if (packetlistener.shouldHandleMessage(packet)) { + try { + Connection.genericsFtw(packet, packetlistener); + } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { + ; ++ } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop + } catch (RejectedExecutionException rejectedexecutionexception) { + this.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown")); + } catch (ClassCastException classcastexception) { +@@ -205,7 +318,7 @@ + } + + private static void genericsFtw(Packet packet, PacketListener listener) { +- packet.handle(listener); ++ packet.handle((T) listener); // CraftBukkit - decompile error + } + + private void validateListener(ProtocolInfo state, PacketListener listener) { +@@ -418,12 +531,26 @@ + } + } + ++ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world ++ private static int joinAttemptsThisTick; // Paper - Buffer joins to world ++ private static int currTick; // Paper - Buffer joins to world + public void tick() { + this.flushQueue(); ++ // Paper start - Buffer joins to world ++ if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { ++ Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; ++ Connection.joinAttemptsThisTick = 0; ++ } ++ // Paper end - Buffer joins to world + PacketListener packetlistener = this.packetListener; + + if (packetlistener instanceof TickablePacketListener tickablepacketlistener) { ++ // Paper start - Buffer joins to world ++ if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) ++ || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING ++ || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { + tickablepacketlistener.tick(); ++ } // Paper end - Buffer joins to world + } + + if (!this.isConnected() && !this.disconnectionHandled) { +@@ -431,7 +558,7 @@ + } + + if (this.channel != null) { +- this.channel.flush(); ++ if (enableExplicitFlush) this.channel.eventLoop().execute(() -> this.channel.flush()); // Paper - Disable explicit network manager flushing; we don't need to explicit flush here, but allow opt in incase issues are found to a better version + } + + if (this.tickCount++ % 20 == 0) { +@@ -464,12 +591,15 @@ + } + + public void disconnect(DisconnectionDetails disconnectionInfo) { ++ // Spigot Start ++ this.preparing = false; ++ // Spigot End + if (this.channel == null) { + this.delayedDisconnect = disconnectionInfo; + } + + if (this.isConnected()) { +- this.channel.close().awaitUninterruptibly(); ++ this.channel.close(); // We can't wait as this may be called from an event loop. + this.disconnectionDetails = disconnectionInfo; + } + +@@ -537,7 +667,7 @@ + } + + public void configurePacketHandler(ChannelPipeline pipeline) { +- pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter(this) { ++ pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter() { // CraftBukkit - decompile error + public void write(ChannelHandlerContext channelhandlercontext, Object object, ChannelPromise channelpromise) throws Exception { + super.write(channelhandlercontext, object, channelpromise); + } +@@ -613,6 +743,14 @@ + + } + ++ // Paper start - add proper async disconnect ++ public void enableAutoRead() { ++ if (this.channel != null) { ++ this.channel.config().setAutoRead(true); ++ } ++ } ++ // Paper end - add proper async disconnect ++ + public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { + if (compressionThreshold >= 0) { + ChannelHandler channelhandler = this.channel.pipeline().get("decompress"); +@@ -633,6 +771,7 @@ + } else { + this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold)); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners + } else { + if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { + this.channel.pipeline().remove("decompress"); +@@ -641,6 +780,7 @@ + if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { + this.channel.pipeline().remove("compress"); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners + } + + } +@@ -661,6 +801,27 @@ + + packetlistener1.onDisconnect(disconnectiondetails); + } ++ this.pendingActions.clear(); // Free up packet queue. ++ // Paper start - Add PlayerConnectionCloseEvent ++ final PacketListener packetListener = this.getPacketListener(); ++ if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { ++ /* Player was logged in, either game listener or configuration listener */ ++ final com.mojang.authlib.GameProfile profile = commonPacketListener.getOwner(); ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), ++ profile.getName(), ((InetSocketAddress) this.address).getAddress(), false).callEvent(); ++ } else if (packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginListener) { ++ /* Player is login stage */ ++ switch (loginListener.state) { ++ case VERIFYING: ++ case WAITING_FOR_DUPE_DISCONNECT: ++ case PROTOCOL_SWITCHING: ++ case ACCEPTED: ++ final com.mojang.authlib.GameProfile profile = loginListener.authenticatedProfile; /* Should be non-null at this stage */ ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(), ++ ((InetSocketAddress) this.address).getAddress(), false).callEvent(); ++ } ++ } ++ // Paper end - Add PlayerConnectionCloseEvent + + } + } diff --git a/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch b/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch new file mode 100644 index 0000000000..c9d512f8d6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch @@ -0,0 +1,105 @@ +--- a/net/minecraft/network/FriendlyByteBuf.java ++++ b/net/minecraft/network/FriendlyByteBuf.java +@@ -72,6 +72,7 @@ + + public static final int DEFAULT_NBT_QUOTA = 2097152; + private final ByteBuf source; ++ @Nullable public final java.util.Locale adventure$locale; // Paper - track player's locale for server-side translations + public static final short MAX_STRING_LENGTH = Short.MAX_VALUE; + public static final int MAX_COMPONENT_STRING_LENGTH = 262144; + private static final int PUBLIC_KEY_SIZE = 256; +@@ -80,6 +81,7 @@ + private static final Gson GSON = new Gson(); + + public FriendlyByteBuf(ByteBuf parent) { ++ this.adventure$locale = PacketEncoder.ADVENTURE_LOCALE.get(); // Paper - track player's locale for server-side translations + this.source = parent; + } + +@@ -120,11 +122,16 @@ + } + + public void writeJsonWithCodec(Codec codec, T value) { ++ // Paper start - Adventure; add max length parameter ++ this.writeJsonWithCodec(codec, value, MAX_STRING_LENGTH); ++ } ++ public void writeJsonWithCodec(Codec codec, T value, int maxLength) { ++ // Paper end - Adventure; add max length parameter + DataResult dataresult = codec.encodeStart(JsonOps.INSTANCE, value); + + this.writeUtf(FriendlyByteBuf.GSON.toJson((JsonElement) dataresult.getOrThrow((s) -> { + return new EncoderException("Failed to encode: " + s + " " + String.valueOf(value)); +- }))); ++ })), maxLength); // Paper - Adventure; add max length parameter + } + + public static IntFunction limitValue(IntFunction applier, int max) { +@@ -139,7 +146,7 @@ + + public > C readCollection(IntFunction collectionFactory, StreamDecoder reader) { + int i = this.readVarInt(); +- C c0 = (Collection) collectionFactory.apply(i); ++ C c0 = collectionFactory.apply(i); // CraftBukkit - decompile error + + for (int j = 0; j < i; ++j) { + c0.add(reader.decode(this)); +@@ -150,7 +157,7 @@ + + public void writeCollection(Collection collection, StreamEncoder writer) { + this.writeVarInt(collection.size()); +- Iterator iterator = collection.iterator(); ++ Iterator iterator = collection.iterator(); // CraftBukkit - decompile error + + while (iterator.hasNext()) { + T t0 = iterator.next(); +@@ -177,12 +184,12 @@ + + public void writeIntIdList(IntList list) { + this.writeVarInt(list.size()); +- list.forEach(this::writeVarInt); ++ list.forEach((java.util.function.IntConsumer) this::writeVarInt); // CraftBukkit - decompile error + } + + public > M readMap(IntFunction mapFactory, StreamDecoder keyReader, StreamDecoder valueReader) { + int i = this.readVarInt(); +- M m0 = (Map) mapFactory.apply(i); ++ M m0 = mapFactory.apply(i); // CraftBukkit - decompile error + + for (int j = 0; j < i; ++j) { + K k0 = keyReader.decode(this); +@@ -216,7 +223,7 @@ + } + + public > void writeEnumSet(EnumSet enumSet, Class type) { +- E[] ae = (Enum[]) type.getEnumConstants(); ++ E[] ae = type.getEnumConstants(); // CraftBukkit - decompile error + BitSet bitset = new BitSet(ae.length); + + for (int i = 0; i < ae.length; ++i) { +@@ -227,7 +234,7 @@ + } + + public > EnumSet readEnumSet(Class type) { +- E[] ae = (Enum[]) type.getEnumConstants(); ++ E[] ae = type.getEnumConstants(); // CraftBukkit - decompile error + BitSet bitset = this.readFixedBitSet(ae.length); + EnumSet enumset = EnumSet.noneOf(type); + +@@ -498,7 +505,7 @@ + } + + public > T readEnum(Class enumClass) { +- return ((Enum[]) enumClass.getEnumConstants())[this.readVarInt()]; ++ return ((T[]) enumClass.getEnumConstants())[this.readVarInt()]; // CraftBukkit - fix decompile error + } + + public FriendlyByteBuf writeEnum(Enum instance) { +@@ -565,7 +572,7 @@ + + try { + NbtIo.writeAnyTag((Tag) nbt, new ByteBufOutputStream(buf)); +- } catch (IOException ioexception) { ++ } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception + throw new EncoderException(ioexception); + } + } diff --git a/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch new file mode 100644 index 0000000000..ce1cafa04d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch @@ -0,0 +1,57 @@ +--- a/net/minecraft/network/PacketEncoder.java ++++ b/net/minecraft/network/PacketEncoder.java +@@ -17,10 +17,12 @@ + this.protocolInfo = state; + } + ++ static final ThreadLocal ADVENTURE_LOCALE = ThreadLocal.withInitial(() -> null); // Paper - adventure; set player's locale + protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf byteBuf) throws Exception { + PacketType> packetType = packet.type(); + + try { ++ ADVENTURE_LOCALE.set(channelHandlerContext.channel().attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).get()); // Paper - adventure; set player's locale + this.protocolInfo.codec().encode(byteBuf, packet); + int i = byteBuf.readableBytes(); + if (LOGGER.isDebugEnabled()) { +@@ -31,14 +33,40 @@ + + JvmProfiler.INSTANCE.onPacketSent(this.protocolInfo.id(), packetType, channelHandlerContext.channel().remoteAddress(), i); + } catch (Throwable var9) { +- LOGGER.error("Error sending packet {}", packetType, var9); ++ LOGGER.error("Error sending packet {} (skippable? {})", packetType, packet.isSkippable(), var9); + if (packet.isSkippable()) { + throw new SkipPacketException(var9); + } + + throw var9; + } finally { ++ // Paper start - Handle large packets disconnecting client ++ int packetLength = byteBuf.readableBytes(); ++ if (packetLength > MAX_PACKET_SIZE || (packetLength > MAX_FINAL_PACKET_SIZE && packet.hasLargePacketFallback())) { ++ throw new PacketTooLargeException(packet, packetLength); ++ } ++ // Paper end - Handle large packets disconnecting client + ProtocolSwapHandler.handleOutboundTerminalPacket(channelHandlerContext, packet); + } + } ++ ++ // Paper start ++ // packet size is encoded into 3-byte varint ++ private static final int MAX_FINAL_PACKET_SIZE = (1 << 21) - 1; ++ // Vanilla Max size for the encoder (before compression) ++ private static final int MAX_PACKET_SIZE = 8388608; ++ ++ public static class PacketTooLargeException extends RuntimeException { ++ private final Packet packet; ++ ++ PacketTooLargeException(Packet packet, int packetLength) { ++ super("PacketTooLarge - " + packet.getClass().getSimpleName() + " is " + packetLength + ". Max is " + MAX_PACKET_SIZE); ++ this.packet = packet; ++ } ++ ++ public Packet getPacket() { ++ return this.packet; ++ } ++ } ++ // Paper end + } diff --git a/paper-server/patches/sources/net/minecraft/network/VarInt.java.patch b/paper-server/patches/sources/net/minecraft/network/VarInt.java.patch new file mode 100644 index 0000000000..e2a07664c5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/VarInt.java.patch @@ -0,0 +1,43 @@ +--- a/net/minecraft/network/VarInt.java ++++ b/net/minecraft/network/VarInt.java +@@ -9,6 +9,18 @@ + private static final int DATA_BITS_PER_BYTE = 7; + + public static int getByteSize(int i) { ++ // Paper start - Optimize VarInts ++ return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(i)]; ++ } ++ private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; ++ static { ++ for (int i = 0; i <= 32; ++i) { ++ VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); ++ } ++ VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0. ++ } ++ public static int getByteSizeOld(int i) { ++ // Paper end - Optimize VarInts + for (int j = 1; j < 5; j++) { + if ((i & -1 << j * 7) == 0) { + return j; +@@ -39,6 +51,21 @@ + } + + public static ByteBuf write(ByteBuf buf, int i) { ++ // Paper start - Optimize VarInts ++ // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes ++ // that the proxy will write, to improve inlining. ++ if ((i & (0xFFFFFFFF << 7)) == 0) { ++ buf.writeByte(i); ++ } else if ((i & (0xFFFFFFFF << 14)) == 0) { ++ int w = (i & 0x7F | 0x80) << 8 | (i >>> 7); ++ buf.writeShort(w); ++ } else { ++ writeOld(buf, i); ++ } ++ return buf; ++ } ++ public static ByteBuf writeOld(ByteBuf buf, int i) { ++ // Paper end - Optimize VarInts + while ((i & -128) != 0) { + buf.writeByte(i & 127 | 128); + i >>>= 7; diff --git a/paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch b/paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch new file mode 100644 index 0000000000..e9aad632b1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/network/Varint21FrameDecoder.java ++++ b/net/minecraft/network/Varint21FrameDecoder.java +@@ -39,6 +39,12 @@ + } + + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) { ++ // Paper start - Perf: Optimize exception handling; if channel is not active just discard the packet ++ if (!channelHandlerContext.channel().isActive()) { ++ byteBuf.skipBytes(byteBuf.readableBytes()); ++ return; ++ } ++ // Paper end - Perf: Optimize exception handling + byteBuf.markReaderIndex(); + this.helperBuf.clear(); + if (!copyVarint(byteBuf, this.helperBuf)) { diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch new file mode 100644 index 0000000000..8fe79b8a75 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/network/chat/ChatDecorator.java ++++ b/net/minecraft/network/chat/ChatDecorator.java +@@ -2,10 +2,18 @@ + + import javax.annotation.Nullable; + import net.minecraft.server.level.ServerPlayer; ++import java.util.concurrent.CompletableFuture; // Paper + + @FunctionalInterface + public interface ChatDecorator { +- ChatDecorator PLAIN = (sender, message) -> message; ++ ChatDecorator PLAIN = (sender, message) -> CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events + +- Component decorate(@Nullable ServerPlayer sender, Component message); ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events (callers should use the overload with CommandSourceStack) ++ CompletableFuture decorate(@Nullable ServerPlayer sender, Component message); // Paper - adventure; support async chat decoration events ++ ++ // Paper start - adventure; support async chat decoration events ++ default CompletableFuture decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) { ++ throw new UnsupportedOperationException("Must override this implementation"); ++ } ++ // Paper end - adventure; support async chat decoration events + } diff --git a/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch new file mode 100644 index 0000000000..5db0b612a2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/network/chat/Component.java ++++ b/net/minecraft/network/chat/Component.java +@@ -37,9 +37,23 @@ + import net.minecraft.resources.ResourceLocation; + import net.minecraft.util.FormattedCharSequence; + import net.minecraft.world.level.ChunkPos; ++// CraftBukkit start ++import java.util.stream.Stream; ++// CraftBukkit end + +-public interface Component extends Message, FormattedText { ++public interface Component extends Message, FormattedText, Iterable { // CraftBukkit ++ ++ // CraftBukkit start ++ default Stream stream() { ++ return com.google.common.collect.Streams.concat(new Stream[]{Stream.of(this), this.getSiblings().stream().flatMap(Component::stream)}); ++ } + ++ @Override ++ default Iterator iterator() { ++ return this.stream().iterator(); ++ } ++ // CraftBukkit end ++ + Style getStyle(); + + ComponentContents getContents(); diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch new file mode 100644 index 0000000000..4136889ef4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch @@ -0,0 +1,99 @@ +--- a/net/minecraft/network/chat/ComponentSerialization.java ++++ b/net/minecraft/network/chat/ComponentSerialization.java +@@ -37,9 +37,31 @@ + + public class ComponentSerialization { + public static final Codec CODEC = Codec.recursive("Component", ComponentSerialization::createCodec); +- public static final StreamCodec STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistries(CODEC); ++ public static final StreamCodec STREAM_CODEC = createTranslationAware(() -> net.minecraft.nbt.NbtAccounter.create(net.minecraft.network.FriendlyByteBuf.DEFAULT_NBT_QUOTA)); // Paper - adventure + public static final StreamCodec> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional); +- public static final StreamCodec TRUSTED_STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(CODEC); ++ // Paper start - adventure; use locale from bytebuf for translation ++ public static final ThreadLocal DONT_RENDER_TRANSLATABLES = ThreadLocal.withInitial(() -> false); ++ public static final StreamCodec TRUSTED_STREAM_CODEC = createTranslationAware(net.minecraft.nbt.NbtAccounter::unlimitedHeap); ++ private static StreamCodec createTranslationAware(final Supplier sizeTracker) { ++ return new StreamCodec<>() { ++ final StreamCodec streamCodec = ByteBufCodecs.tagCodec(sizeTracker); ++ @Override ++ public Component decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) { ++ net.minecraft.nbt.Tag tag = this.streamCodec.decode(registryFriendlyByteBuf); ++ RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); ++ return CODEC.parse(registryOps, tag).getOrThrow(error -> new io.netty.handler.codec.DecoderException("Failed to decode: " + error + " " + tag)); ++ } ++ ++ @Override ++ public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, Component object) { ++ RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); ++ net.minecraft.nbt.Tag tag = (DONT_RENDER_TRANSLATABLES.get() ? CODEC : ComponentSerialization.localizedCodec(registryFriendlyByteBuf.adventure$locale)) ++ .encodeStart(registryOps, object).getOrThrow(error -> new io.netty.handler.codec.EncoderException("Failed to encode: " + error + " " + object)); ++ this.streamCodec.encode(registryFriendlyByteBuf, tag); ++ } ++ }; ++ } ++ // Paper end - adventure; use locale from bytebuf for translation + public static final StreamCodec> TRUSTED_OPTIONAL_STREAM_CODEC = TRUSTED_STREAM_CODEC.apply( + ByteBufCodecs::optional + ); +@@ -100,7 +122,27 @@ + return ExtraCodecs.orCompressed(mapCodec3, mapCodec2); + } + ++ // Paper start - adventure; create separate codec for each locale ++ private static final java.util.Map> LOCALIZED_CODECS = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ public static Codec localizedCodec(final java.util.@org.checkerframework.checker.nullness.qual.Nullable Locale locale) { ++ if (locale == null) { ++ return CODEC; ++ } ++ return LOCALIZED_CODECS.computeIfAbsent(locale, ++ loc -> Codec.recursive("Component", selfCodec -> createCodec(selfCodec, loc))); ++ } ++ ++ ++ // Paper end - adventure; create separate codec for each locale ++ + private static Codec createCodec(Codec selfCodec) { ++ // Paper start - adventure; create separate codec for each locale ++ return createCodec(selfCodec, null); ++ } ++ ++ private static Codec createCodec(Codec selfCodec, @javax.annotation.Nullable java.util.Locale locale) { ++ // Paper end - adventure; create separate codec for each locale + ComponentContents.Type[] types = new ComponentContents.Type[]{ + PlainTextContents.TYPE, TranslatableContents.TYPE, KeybindContents.TYPE, ScoreContents.TYPE, SelectorContents.TYPE, NbtContents.TYPE + }; +@@ -113,6 +155,34 @@ + ) + .apply(instance, MutableComponent::new) + ); ++ // Paper start - adventure; create separate codec for each locale ++ final Codec origCodec = codec; ++ codec = new Codec<>() { ++ @Override ++ public DataResult> decode(final DynamicOps ops, final T input) { ++ return origCodec.decode(ops, input); ++ } ++ ++ @Override ++ public DataResult encode(final Component input, final DynamicOps ops, final T prefix) { ++ final net.kyori.adventure.text.Component adventureComponent; ++ if (input instanceof io.papermc.paper.adventure.AdventureComponent adv) { ++ adventureComponent = adv.adventure$component(); ++ } else if (locale != null && input.getContents() instanceof TranslatableContents && io.papermc.paper.adventure.PaperAdventure.hasAnyTranslations()) { ++ adventureComponent = io.papermc.paper.adventure.PaperAdventure.asAdventure(input); ++ } else { ++ return origCodec.encode(input, ops, prefix); ++ } ++ return io.papermc.paper.adventure.PaperAdventure.localizedCodec(locale) ++ .encode(adventureComponent, ops, prefix); ++ } ++ ++ @Override ++ public String toString() { ++ return origCodec.toString() + "[AdventureComponentAware]"; ++ } ++ }; ++ // Paper end - adventure; create separate codec for each locale + return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), codec) + .xmap(either -> either.map(either2 -> either2.map(Component::literal, ComponentSerialization::createFromList), text -> (Component)text), text -> { + String string = text.tryCollapseToString(); diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch new file mode 100644 index 0000000000..c7b1b54e5a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/network/chat/ComponentUtils.java ++++ b/net/minecraft/network/chat/ComponentUtils.java +@@ -33,14 +33,39 @@ + } + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper - validate separators - right now this method is only used for separator evaluation. Error on build if this changes to re-evaluate. + public static Optional updateForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { + return text.isPresent() ? Optional.of(updateForEntity(source, text.get(), sender, depth)) : Optional.empty(); + } + ++ // Paper start - validate separator ++ public static Optional updateSeparatorForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { ++ if (text.isEmpty() || !isValidSelector(text.get())) return Optional.empty(); ++ return Optional.of(updateForEntity(source, text.get(), sender, depth)); ++ } ++ public static boolean isValidSelector(final Component component) { ++ final ComponentContents contents = component.getContents(); ++ ++ if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents) return false; ++ if (contents instanceof final net.minecraft.network.chat.contents.TranslatableContents translatableContents) { ++ for (final Object arg : translatableContents.getArgs()) { ++ if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent)) return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - validate separator ++ + public static MutableComponent updateForEntity(@Nullable CommandSourceStack source, Component text, @Nullable Entity sender, int depth) throws CommandSyntaxException { + if (depth > 100) { + return text.copy(); + } else { ++ // Paper start - adventure; pass actual vanilla component ++ if (text instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) { ++ text = adventureComponent.deepConverted(); ++ } ++ // Paper end - adventure; pass actual vanilla component + MutableComponent mutableComponent = text.getContents().resolve(source, sender, depth + 1); + + for (Component component : text.getSiblings()) { diff --git a/paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch new file mode 100644 index 0000000000..2b6f690732 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/network/chat/MessageSignature.java ++++ b/net/minecraft/network/chat/MessageSignature.java +@@ -13,6 +13,7 @@ + import net.minecraft.util.SignatureValidator; + + public record MessageSignature(byte[] bytes) { ++ public net.kyori.adventure.chat.SignedMessage.Signature adventure() { return () -> this.bytes; } // Paper - adventure; support signed messages + public static final Codec CODEC = ExtraCodecs.BASE64_STRING.xmap(MessageSignature::new, MessageSignature::bytes); + public static final int BYTES = 256; + diff --git a/paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch new file mode 100644 index 0000000000..c4694faca6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/network/chat/MutableComponent.java ++++ b/net/minecraft/network/chat/MutableComponent.java +@@ -94,6 +94,11 @@ + + @Override + public boolean equals(Object object) { ++ // Paper start - make AdventureComponent equivalent ++ if (object instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) { ++ object = adventureComponent.deepConverted(); ++ } ++ // Paper end - make AdventureComponent equivalent + return this == object + || object instanceof MutableComponent mutableComponent + && this.contents.equals(mutableComponent.contents) diff --git a/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch new file mode 100644 index 0000000000..3192e18db3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/network/chat/OutgoingChatMessage.java ++++ b/net/minecraft/network/chat/OutgoingChatMessage.java +@@ -7,6 +7,12 @@ + + void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params); + ++ // Paper start ++ default void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) { ++ this.sendToPlayer(sender, filterMaskEnabled, params); ++ } ++ // Paper end ++ + static OutgoingChatMessage create(PlayerChatMessage message) { + return (OutgoingChatMessage)(message.isSystem() + ? new OutgoingChatMessage.Disguised(message.decoratedContent()) +@@ -16,8 +22,13 @@ + public static record Disguised(@Override Component content) implements OutgoingChatMessage { + @Override + public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params) { +- sender.connection.sendDisguisedChatMessage(this.content, params); ++ // Paper start ++ this.sendToPlayer(sender, filterMaskEnabled, params, null); + } ++ public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) { ++ sender.connection.sendDisguisedChatMessage(unsigned != null ? unsigned : this.content, params); ++ // Paper end ++ } + } + + public static record Player(PlayerChatMessage message) implements OutgoingChatMessage { +@@ -28,7 +39,13 @@ + + @Override + public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params) { ++ // Paper start ++ this.sendToPlayer(sender, filterMaskEnabled, params, null); ++ } ++ public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) { ++ // Paper end + PlayerChatMessage playerChatMessage = this.message.filter(filterMaskEnabled); ++ playerChatMessage = unsigned != null ? playerChatMessage.withUnsignedContent(unsigned) : playerChatMessage; // Paper + if (!playerChatMessage.isFullyFiltered()) { + sender.connection.sendPlayerChatMessage(playerChatMessage, params); + } diff --git a/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch new file mode 100644 index 0000000000..8722bc8269 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/network/chat/PlayerChatMessage.java ++++ b/net/minecraft/network/chat/PlayerChatMessage.java +@@ -17,6 +17,42 @@ + public record PlayerChatMessage( + SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask + ) { ++ // Paper start - adventure; support signed messages ++ public final class AdventureView implements net.kyori.adventure.chat.SignedMessage { ++ private AdventureView() { ++ } ++ @Override ++ public @org.jetbrains.annotations.NotNull Instant timestamp() { ++ return PlayerChatMessage.this.timeStamp(); ++ } ++ @Override ++ public long salt() { ++ return PlayerChatMessage.this.salt(); ++ } ++ @Override ++ public @org.jetbrains.annotations.Nullable Signature signature() { ++ return PlayerChatMessage.this.signature == null ? null : PlayerChatMessage.this.signature.adventure(); ++ } ++ @Override ++ public net.kyori.adventure.text.@org.jetbrains.annotations.Nullable Component unsignedContent() { ++ return PlayerChatMessage.this.unsignedContent() == null ? null : io.papermc.paper.adventure.PaperAdventure.asAdventure(PlayerChatMessage.this.unsignedContent()); ++ } ++ @Override ++ public @org.jetbrains.annotations.NotNull String message() { ++ return PlayerChatMessage.this.signedContent(); ++ } ++ @Override ++ public @org.jetbrains.annotations.NotNull net.kyori.adventure.identity.Identity identity() { ++ return net.kyori.adventure.identity.Identity.identity(PlayerChatMessage.this.sender()); ++ } ++ public PlayerChatMessage playerChatMessage() { ++ return PlayerChatMessage.this; ++ } ++ } ++ public AdventureView adventureView() { ++ return new AdventureView(); ++ } ++ // Paper end - adventure; support signed messages + public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec( + instance -> instance.group( + SignedMessageLink.CODEC.fieldOf("link").forGetter(PlayerChatMessage::link), +@@ -47,7 +83,14 @@ + } + + public PlayerChatMessage withUnsignedContent(Component unsignedContent) { +- Component component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null; ++ // Paper start - adventure ++ final Component component; ++ if (unsignedContent instanceof io.papermc.paper.adventure.AdventureComponent advComponent) { ++ component = this.signedContent().equals(io.papermc.paper.adventure.AdventureCodecs.tryCollapseToString(advComponent.adventure$component())) ? null : unsignedContent; ++ } else { ++ component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null; ++ } ++ // Paper end - adventure + return new PlayerChatMessage(this.link, this.signature, this.signedBody, component, this.filterMask); + } + diff --git a/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch new file mode 100644 index 0000000000..9404843b29 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/network/chat/SignedMessageChain.java ++++ b/net/minecraft/network/chat/SignedMessageChain.java +@@ -40,14 +40,14 @@ + if (signature == null) { + throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.MISSING_PROFILE_KEY); + } else if (playerPublicKey.data().hasExpired()) { +- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY); ++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes + } else { + SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink; + if (signedMessageLink == null) { + throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); + } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { + this.setChainBroken(); +- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT); ++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes + } else { + SignedMessageChain.this.lastTimeStamp = body.timeStamp(); + PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH); +@@ -80,9 +80,16 @@ + static final Component INVALID_SIGNATURE = Component.translatable("chat.disabled.invalid_signature"); + static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat"); + +- public DecodeException(Component message) { ++ // Paper start ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; ++ public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) { + super(message); ++ this.kickCause = event; + } ++ // Paper end ++ public DecodeException(Component message) { ++ this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper ++ } + } + + @FunctionalInterface diff --git a/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch new file mode 100644 index 0000000000..014fd3eb10 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/network/chat/TextColor.java ++++ b/net/minecraft/network/chat/TextColor.java +@@ -17,7 +17,7 @@ + private static final String CUSTOM_COLOR_PREFIX = "#"; + public static final Codec CODEC = Codec.STRING.comapFlatMap(TextColor::parseColor, TextColor::serialize); + private static final Map LEGACY_FORMAT_TO_COLOR = (Map) Stream.of(ChatFormatting.values()).filter(ChatFormatting::isColor).collect(ImmutableMap.toImmutableMap(Function.identity(), (enumchatformat) -> { +- return new TextColor(enumchatformat.getColor(), enumchatformat.getName()); ++ return new TextColor(enumchatformat.getColor(), enumchatformat.getName(), enumchatformat); // CraftBukkit + })); + private static final Map NAMED_COLORS = (Map) TextColor.LEGACY_FORMAT_TO_COLOR.values().stream().collect(ImmutableMap.toImmutableMap((chathexcolor) -> { + return chathexcolor.name; +@@ -25,16 +25,22 @@ + private final int value; + @Nullable + public final String name; ++ // CraftBukkit start ++ @Nullable ++ public final ChatFormatting format; + +- private TextColor(int rgb, String name) { +- this.value = rgb & 16777215; +- this.name = name; ++ private TextColor(int i, String s, ChatFormatting format) { ++ this.value = i & 16777215; ++ this.name = s; ++ this.format = format; + } + + private TextColor(int rgb) { + this.value = rgb & 16777215; + this.name = null; ++ this.format = null; + } ++ // CraftBukkit end + + public int getValue() { + return this.value; diff --git a/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch new file mode 100644 index 0000000000..f30203349e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/network/chat/contents/NbtContents.java ++++ b/net/minecraft/network/chat/contents/NbtContents.java +@@ -120,7 +120,7 @@ + }).map(Tag::getAsString); + if (this.interpreting) { + Component component = DataFixUtils.orElse( +- ComponentUtils.updateForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR ++ ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator + ); + return stream.flatMap(text -> { + try { +@@ -132,7 +132,7 @@ + } + }).reduce((accumulator, current) -> accumulator.append(component).append(current)).orElseGet(Component::empty); + } else { +- return ComponentUtils.updateForEntity(source, this.separator, sender, depth) ++ return ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth) // Paper - validate separator + .map( + text -> stream.map(Component::literal) + .reduce((accumulator, current) -> accumulator.append(text).append(current)) diff --git a/paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch new file mode 100644 index 0000000000..aff540bbdc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/chat/contents/SelectorContents.java ++++ b/net/minecraft/network/chat/contents/SelectorContents.java +@@ -36,7 +36,7 @@ + if (source == null) { + return Component.empty(); + } else { +- Optional optional = ComponentUtils.updateForEntity(source, this.separator, sender, depth); ++ Optional optional = ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth); // Paper - validate separator + return ComponentUtils.formatList(this.selector.resolved().findEntities(source), optional, Entity::getDisplayName); + } + } diff --git a/paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch new file mode 100644 index 0000000000..88b91fae6d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/network/chat/contents/TranslatableContents.java ++++ b/net/minecraft/network/chat/contents/TranslatableContents.java +@@ -181,6 +181,15 @@ + + @Override + public Optional visit(FormattedText.ContentConsumer visitor) { ++ // Paper start - Count visited parts ++ try { ++ return this.visit(new TranslatableContentConsumer<>(visitor)); ++ } catch (IllegalArgumentException ignored) { ++ return visitor.accept("..."); ++ } ++ } ++ private Optional visit(TranslatableContentConsumer visitor) { ++ // Paper end - Count visited parts + this.decompose(); + + for (FormattedText formattedText : this.decomposedParts) { +@@ -191,7 +200,26 @@ + } + + return Optional.empty(); ++ } ++ // Paper start - Count visited parts ++ private static final class TranslatableContentConsumer implements FormattedText.ContentConsumer { ++ private static final IllegalArgumentException EX = new IllegalArgumentException("Too long"); ++ private final FormattedText.ContentConsumer visitor; ++ private int visited; ++ ++ private TranslatableContentConsumer(FormattedText.ContentConsumer visitor) { ++ this.visitor = visitor; ++ } ++ ++ @Override ++ public Optional accept(final String asString) { ++ if (visited++ > 32) { ++ throw EX; ++ } ++ return this.visitor.accept(asString); ++ } + } ++ // Paper end - Count visited parts + + @Override + public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch new file mode 100644 index 0000000000..ba40a5940e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/network/protocol/Packet.java ++++ b/net/minecraft/network/protocol/Packet.java +@@ -11,6 +11,19 @@ + + void handle(T listener); + ++ // Paper start ++ default boolean hasLargePacketFallback() { ++ return false; ++ } ++ ++ /** ++ * override {@link #hasLargePacketFallback()} to return true when overriding in subclasses ++ */ ++ default boolean packetTooLarge(net.minecraft.network.Connection manager) { ++ return false; ++ } ++ // Paper end ++ + default boolean isSkippable() { + return false; + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch new file mode 100644 index 0000000000..67d49cd3f7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/network/protocol/PacketUtils.java ++++ b/net/minecraft/network/protocol/PacketUtils.java +@@ -6,10 +6,15 @@ + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; + import net.minecraft.network.PacketListener; ++import org.slf4j.Logger; ++ ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.RunningOnDifferentThreadException; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.network.ServerCommonPacketListenerImpl; ++// CraftBukkit end + import net.minecraft.util.thread.BlockableEventLoop; +-import org.slf4j.Logger; + + public class PacketUtils { + +@@ -24,6 +29,7 @@ + public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { + if (!engine.isSameThread()) { + engine.executeIfPossible(() -> { ++ if (listener instanceof ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // CraftBukkit - Don't handle sync packets for kicked players + if (listener.shouldHandleMessage(packet)) { + try { + packet.handle(listener); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch new file mode 100644 index 0000000000..e17dc6200b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java ++++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java +@@ -2,7 +2,6 @@ + + import com.google.common.collect.Lists; + import java.util.List; +-import net.minecraft.Util; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.codec.StreamCodec; + import net.minecraft.network.protocol.Packet; +@@ -16,8 +15,7 @@ + private static final int MAX_PAYLOAD_SIZE = 32767; + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec((minecraftkey) -> { + return DiscardedPayload.codec(minecraftkey, 32767); +- }, (List) Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec[]{new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)}), (arraylist) -> { +- })).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload); ++ }, java.util.Collections.emptyList()).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload); // CraftBukkit - treat all packets the same + + @Override + public PacketType type() { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch new file mode 100644 index 0000000000..785d4efd47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java ++++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java +@@ -4,16 +4,18 @@ + import net.minecraft.network.codec.StreamCodec; + import net.minecraft.resources.ResourceLocation; + +-public record DiscardedPayload(ResourceLocation id) implements CustomPacketPayload { ++public record DiscardedPayload(ResourceLocation id, io.netty.buffer.ByteBuf data) implements CustomPacketPayload { // CraftBukkit - store data + + public static StreamCodec codec(ResourceLocation id, int maxBytes) { + return CustomPacketPayload.codec((discardedpayload, packetdataserializer) -> { ++ packetdataserializer.writeBytes(discardedpayload.data); // CraftBukkit - serialize + }, (packetdataserializer) -> { + int j = packetdataserializer.readableBytes(); + + if (j >= 0 && j <= maxBytes) { +- packetdataserializer.skipBytes(j); +- return new DiscardedPayload(id); ++ // CraftBukkit start ++ return new DiscardedPayload(id, packetdataserializer.readBytes(j)); ++ // CraftBukkit end + } else { + throw new IllegalArgumentException("Payload may not be larger than " + maxBytes + " bytes"); + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch new file mode 100644 index 0000000000..4967c17ea2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java +@@ -29,7 +29,7 @@ + + public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity, BiFunction nbtGetter) { + RegistryAccess registryAccess = blockEntity.getLevel().registryAccess(); +- return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity, registryAccess)); ++ return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity, registryAccess))); // Paper - Sanitize sent data + } + + public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch new file mode 100644 index 0000000000..1a14cb1295 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +@@ -36,6 +36,21 @@ + this.carriedItem = ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); + } + ++ // Paper start - Handle large packets disconnecting client ++ @Override ++ public boolean hasLargePacketFallback() { ++ return true; ++ } ++ ++ @Override ++ public boolean packetTooLarge(net.minecraft.network.Connection manager) { ++ for (int i = 0 ; i < this.items.size() ; i++) { ++ manager.send(new ClientboundContainerSetSlotPacket(this.containerId, this.stateId, i, this.items.get(i))); ++ } ++ return true; ++ } ++ // Paper end - Handle large packets disconnecting client ++ + private void write(RegistryFriendlyByteBuf buf) { + buf.writeContainerId(this.containerId); + buf.writeVarInt(this.stateId); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch new file mode 100644 index 0000000000..a1740eccbe --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java +@@ -30,8 +30,10 @@ + } + + public ClientboundInitializeBorderPacket(WorldBorder worldBorder) { +- this.newCenterX = worldBorder.getCenterX(); +- this.newCenterZ = worldBorder.getCenterZ(); ++ // CraftBukkit start - multiply out nether border ++ this.newCenterX = worldBorder.getCenterX() * worldBorder.world.dimensionType().coordinateScale(); ++ this.newCenterZ = worldBorder.getCenterZ() * worldBorder.world.dimensionType().coordinateScale(); ++ // CraftBukkit end + this.oldSize = worldBorder.getSize(); + this.newSize = worldBorder.getLerpTarget(); + this.lerpTime = worldBorder.getLerpRemainingTime(); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch new file mode 100644 index 0000000000..26f8a0aa6a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -52,7 +52,7 @@ + throw new RuntimeException("Can't read heightmap in packet for [" + x + ", " + z + "]"); + } else { + int i = buf.readVarInt(); +- if (i > 2097152) { ++ if (i > 2097152) { // Paper - diff on change - if this changes, update PacketEncoder + throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); + } else { + this.buffer = new byte[i]; +@@ -154,6 +154,7 @@ + CompoundTag compoundTag = blockEntity.getUpdateTag(blockEntity.getLevel().registryAccess()); + BlockPos blockPos = blockEntity.getBlockPos(); + int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ()); ++ blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data + return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag); + } + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch new file mode 100644 index 0000000000..edb5b971ee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch @@ -0,0 +1,114 @@ +--- a/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +@@ -38,7 +38,18 @@ + this.actions = EnumSet.of(action); + this.entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(player)); + } ++ // Paper start - Add Listing API for Player ++ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, List entries) { ++ this.actions = actions; ++ this.entries = entries; ++ } + ++ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { ++ this.actions = actions; ++ this.entries = List.of(entry); ++ } ++ // Paper end - Add Listing API for Player ++ + public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players) { + EnumSet enumSet = EnumSet.of( + ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, +@@ -53,6 +64,46 @@ + return new ClientboundPlayerInfoUpdatePacket(enumSet, players); + } + ++ // Paper start - Add Listing API for Player ++ public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players, ServerPlayer forPlayer) { ++ final EnumSet enumSet = EnumSet.of( ++ ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ++ ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER ++ ); ++ final List entries = new java.util.ArrayList<>(players.size()); ++ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); ++ for (final ServerPlayer player : players) { ++ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); ++ } ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { ++ final EnumSet enumSet = EnumSet.of( ++ ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ++ ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER ++ ); ++ final List entries = List.of(new Entry(player, listed)); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { ++ EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); ++ } ++ // Paper end - Add Listing API for Player + private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buf) { + this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); + this.entries = buf.readList(buf2 -> { +@@ -116,7 +167,15 @@ + }), + INITIALIZE_CHAT( + (serialized, buf) -> serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read), +- (buf, entry) -> buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write) ++ // Paper start - Prevent causing expired keys from impacting new joins ++ (buf, entry) -> { ++ RemoteChatSession.Data chatSession = entry.chatSession; ++ if (chatSession != null && chatSession.profilePublicKey().hasExpired()) { ++ chatSession = null; ++ } ++ buf.writeNullable(chatSession, RemoteChatSession.Data::write); ++ } ++ // Paper end - Prevent causing expired keys from impacting new joins + ), + UPDATE_GAME_MODE((serialized, buf) -> serialized.gameMode = GameType.byId(buf.readVarInt()), (buf, entry) -> buf.writeVarInt(entry.gameMode().getId())), + UPDATE_LISTED((serialized, buf) -> serialized.listed = buf.readBoolean(), (buf, entry) -> buf.writeBoolean(entry.listed())), +@@ -157,10 +216,15 @@ + @Nullable RemoteChatSession.Data chatSession + ) { + Entry(ServerPlayer player) { ++ // Paper start - Add Listing API for Player ++ this(player, true); ++ } ++ Entry(ServerPlayer player, boolean listed) { + this( ++ // Paper end - Add Listing API for Player + player.getUUID(), + player.getGameProfile(), +- true, ++ listed, // Paper - Add Listing API for Player + player.connection.latency(), + player.gameMode.getGameModeForPlayer(), + player.getTabListDisplayName(), +@@ -169,6 +233,11 @@ + Optionull.map(player.getChatSession(), RemoteChatSession::asData) + ); + } ++ // Paper start - Add Listing API for Player ++ Entry(UUID profileId, boolean listed) { ++ this(profileId, null, listed, 0, GameType.DEFAULT_MODE, null, true, 0, null); ++ } ++ // Paper end - Add Listing API for Player + } + + static class EntryBuilder { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch new file mode 100644 index 0000000000..372b4afd93 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +@@ -33,11 +33,19 @@ + short short0 = (Short) shortiterator.next(); + + this.positions[j] = short0; +- this.states[j] = section.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0)); ++ this.states[j] = (section != null) ? section.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0)) : net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(); // CraftBukkit - SPIGOT-6076, Mojang bug when empty chunk section notified + } + + } + ++ // CraftBukkit start - Add constructor ++ public ClientboundSectionBlocksUpdatePacket(SectionPos sectionposition, ShortSet shortset, BlockState[] states) { ++ this.sectionPos = sectionposition; ++ this.positions = shortset.toShortArray(); ++ this.states = states; ++ } ++ // CraftBukkit end ++ + private ClientboundSectionBlocksUpdatePacket(FriendlyByteBuf buf) { + this.sectionPos = SectionPos.of(buf.readLong()); + int i = buf.readVarInt(); +@@ -54,6 +62,14 @@ + + } + ++ // Paper start - Multi Block Change API ++ public ClientboundSectionBlocksUpdatePacket(SectionPos sectionPos, it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockChanges) { ++ this.sectionPos = sectionPos; ++ this.positions = blockChanges.keySet().toShortArray(); ++ this.states = blockChanges.values().toArray(new BlockState[0]); ++ } ++ // Paper end - Multi Block Change API ++ + private void write(FriendlyByteBuf buf) { + buf.writeLong(this.sectionPos.asLong()); + buf.writeVarInt(this.positions.length); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch new file mode 100644 index 0000000000..139eaa42d9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java +@@ -13,8 +13,10 @@ + private final double newCenterZ; + + public ClientboundSetBorderCenterPacket(WorldBorder worldBorder) { +- this.newCenterX = worldBorder.getCenterX(); +- this.newCenterZ = worldBorder.getCenterZ(); ++ // CraftBukkit start - multiply out nether border ++ this.newCenterX = worldBorder.getCenterX() * (worldBorder.world != null ? worldBorder.world.dimensionType().coordinateScale() : 1.0); ++ this.newCenterZ = worldBorder.getCenterZ() * (worldBorder.world != null ? worldBorder.world.dimensionType().coordinateScale() : 1.0); ++ // CraftBukkit end + } + + private ClientboundSetBorderCenterPacket(FriendlyByteBuf buf) { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch new file mode 100644 index 0000000000..6a8d6c8ce8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java +@@ -19,9 +19,11 @@ + } + + private static void pack(List> trackedValues, RegistryFriendlyByteBuf buf) { ++ try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(true)) { // Paper - data sanitization + for (SynchedEntityData.DataValue dataValue : trackedValues) { + dataValue.write(buf); + } ++ } // Paper - data sanitization + + buf.writeByte(255); + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch new file mode 100644 index 0000000000..e13e6cd647 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java +@@ -19,6 +19,13 @@ + private final List> slots; + + public ClientboundSetEquipmentPacket(int entityId, List> equipmentList) { ++ // Paper start - data sanitization ++ this(entityId, equipmentList, false); ++ } ++ private boolean sanitize; ++ public ClientboundSetEquipmentPacket(int entityId, List> equipmentList, boolean sanitize) { ++ this.sanitize = sanitize; ++ // Paper end - data sanitization + this.entity = entityId; + this.slots = equipmentList; + } +@@ -40,6 +47,7 @@ + buf.writeVarInt(this.entity); + int i = this.slots.size(); + ++ try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) { // Paper - data sanitization + for (int j = 0; j < i; j++) { + Pair pair = this.slots.get(j); + EquipmentSlot equipmentSlot = pair.getFirst(); +@@ -48,6 +56,7 @@ + buf.writeByte(bl ? k | -128 : k); + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, pair.getSecond()); + } ++ } // Paper - data sanitization + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch new file mode 100644 index 0000000000..1d4da793e7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +@@ -58,6 +58,11 @@ + ); + } + ++ // Paper start - Multiple Entries with Scoreboards ++ public static ClientboundSetPlayerTeamPacket createMultiplePlayerPacket(PlayerTeam team, Collection players, ClientboundSetPlayerTeamPacket.Action operation) { ++ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players); ++ } ++ // Paper end - Multiple Entries with Scoreboards + private ClientboundSetPlayerTeamPacket(RegistryFriendlyByteBuf buf) { + this.name = buf.readUtf(); + this.method = buf.readByte(); +@@ -200,7 +205,7 @@ + ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.displayName); + buf.writeByte(this.options); + buf.writeUtf(this.nametagVisibility); +- buf.writeUtf(this.collisionRule); ++ buf.writeUtf(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions ? "never" : this.collisionRule); // Paper - Configurable player collision + buf.writeEnum(this.color); + ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerPrefix); + ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerSuffix); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch new file mode 100644 index 0000000000..64187dddbd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.network.protocol.game; + + import net.minecraft.network.RegistryFriendlyByteBuf; +@@ -12,6 +13,17 @@ + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ComponentSerialization.TRUSTED_STREAM_CODEC, ClientboundSystemChatPacket::content, ByteBufCodecs.BOOL, ClientboundSystemChatPacket::overlay, ClientboundSystemChatPacket::new); + ++ // Spigot start ++ public ClientboundSystemChatPacket(net.md_5.bungee.api.chat.BaseComponent[] content, boolean overlay) { ++ this(org.bukkit.craftbukkit.util.CraftChatMessage.fromJSON(net.md_5.bungee.chat.ComponentSerializer.toString(content)), overlay); ++ } ++ // Spigot end ++ // Paper start ++ public ClientboundSystemChatPacket(net.kyori.adventure.text.Component content, boolean overlay) { ++ this(io.papermc.paper.adventure.PaperAdventure.asVanilla(content), overlay); ++ } ++ // Paper end ++ + @Override + public PacketType type() { + return GamePacketTypes.CLIENTBOUND_SYSTEM_CHAT; diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch new file mode 100644 index 0000000000..1771a837ae --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +@@ -19,7 +19,7 @@ + + private ServerboundCommandSuggestionPacket(FriendlyByteBuf buf) { + this.id = buf.readVarInt(); +- this.command = buf.readUtf(32500); ++ this.command = buf.readUtf(2048); // Paper + } + + private void write(FriendlyByteBuf buf) { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch new file mode 100644 index 0000000000..a4d170433a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/network/protocol/game/ServerboundInteractPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundInteractPacket.java +@@ -176,4 +176,14 @@ + buf.writeEnum(this.hand); + } + } ++ ++ // Paper start - PlayerUseUnknownEntityEvent ++ public int getEntityId() { ++ return this.entityId; ++ } ++ ++ public boolean isAttack() { ++ return this.action.getType() == ActionType.ATTACK; ++ } ++ // Paper end - PlayerUseUnknownEntityEvent + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch new file mode 100644 index 0000000000..1e2cc2dc52 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.network.protocol.game; + + import net.minecraft.network.FriendlyByteBuf; +@@ -13,6 +14,7 @@ + private final BlockHitResult blockHit; + private final InteractionHand hand; + private final int sequence; ++ public long timestamp; // Spigot + + public ServerboundUseItemOnPacket(InteractionHand hand, BlockHitResult blockHitResult, int sequence) { + this.hand = hand; +@@ -21,6 +23,7 @@ + } + + private ServerboundUseItemOnPacket(FriendlyByteBuf buf) { ++ this.timestamp = System.currentTimeMillis(); // Spigot + this.hand = (InteractionHand) buf.readEnum(InteractionHand.class); + this.blockHit = buf.readBlockHitResult(); + this.sequence = buf.readVarInt(); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch new file mode 100644 index 0000000000..45ba3ec19d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.network.protocol.game; + + import net.minecraft.network.FriendlyByteBuf; +@@ -13,6 +14,7 @@ + private final int sequence; + private final float yRot; + private final float xRot; ++ public long timestamp; // Spigot + + public ServerboundUseItemPacket(InteractionHand hand, int sequence, float yaw, float pitch) { + this.hand = hand; +@@ -22,6 +24,7 @@ + } + + private ServerboundUseItemPacket(FriendlyByteBuf buf) { ++ this.timestamp = System.currentTimeMillis(); // Spigot + this.hand = (InteractionHand) buf.readEnum(InteractionHand.class); + this.sequence = buf.readVarInt(); + this.yRot = buf.readFloat(); diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch new file mode 100644 index 0000000000..ca1a2126c9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -5,16 +5,16 @@ + + public class VecDeltaCodec { + private static final double TRUNCATION_STEPS = 4096.0; +- private Vec3 base = Vec3.ZERO; ++ public Vec3 base = Vec3.ZERO; // Paper + + @VisibleForTesting + static long encode(double value) { +- return Math.round(value * 4096.0); ++ return Math.round(value * 4096.0); // Paper - Fix MC-4; diff on change + } + + @VisibleForTesting + static double decode(long value) { +- return (double)value / 4096.0; ++ return value / 4096.0; // Paper - Fix MC-4; diff on change + } + + public Vec3 decode(long x, long y, long z) { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch new file mode 100644 index 0000000000..071ea04212 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java ++++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.network.protocol.handshake; + + import net.minecraft.network.FriendlyByteBuf; +@@ -11,7 +12,8 @@ + private static final int MAX_HOST_LENGTH = 255; + + private ClientIntentionPacket(FriendlyByteBuf buf) { +- this(buf.readVarInt(), buf.readUtf(255), buf.readUnsignedShort(), ClientIntent.byId(buf.readVarInt())); ++ // Spigot - increase max hostName length ++ this(buf.readVarInt(), buf.readUtf(Short.MAX_VALUE), buf.readUnsignedShort(), ClientIntent.byId(buf.readVarInt())); + } + + private void write(FriendlyByteBuf buf) { diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch new file mode 100644 index 0000000000..60ed80c610 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java ++++ b/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java +@@ -47,4 +47,14 @@ + public void handle(ClientLoginPacketListener listener) { + listener.handleCustomQuery(this); + } ++ ++ // Paper start - MC Utils - default query payloads ++ public static record PlayerInfoChannelPayload(ResourceLocation id, FriendlyByteBuf buffer) implements CustomQueryPayload { ++ ++ @Override ++ public void write(final FriendlyByteBuf buf) { ++ buf.writeBytes(this.buffer.copy()); ++ } ++ } ++ // Paper end - MC Utils - default query payloads + } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch new file mode 100644 index 0000000000..7ad1ef4a1c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java ++++ b/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java +@@ -18,11 +18,16 @@ + } + + private ClientboundLoginDisconnectPacket(FriendlyByteBuf buf) { +- this.reason = Component.Serializer.fromJsonLenient(buf.readUtf(262144), RegistryAccess.EMPTY); ++ this.reason = Component.Serializer.fromJsonLenient(buf.readUtf(FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH), RegistryAccess.EMPTY); // Paper - diff on change + } + + private void write(FriendlyByteBuf buf) { +- buf.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY)); ++ // Paper start - Adventure ++ // buf.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY)); ++ // In the login phase, buf.adventure$locale field is most likely null, but plugins may use internals to set it via the channel attribute ++ java.util.Locale bufLocale = buf.adventure$locale; ++ buf.writeJsonWithCodec(net.minecraft.network.chat.ComponentSerialization.localizedCodec(bufLocale == null ? java.util.Locale.US : bufLocale), this.reason, FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH); ++ // Paper end - Adventure + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch new file mode 100644 index 0000000000..663ca9ede2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch @@ -0,0 +1,43 @@ +--- a/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java ++++ b/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java +@@ -20,7 +20,17 @@ + } + + private static CustomQueryAnswerPayload readPayload(int queryId, FriendlyByteBuf buf) { +- return readUnknownPayload(buf); ++ // Paper start - MC Utils - default query payloads ++ FriendlyByteBuf buffer = buf.readNullable((buf2) -> { ++ int i = buf2.readableBytes(); ++ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) { ++ return new FriendlyByteBuf(buf2.readBytes(i)); ++ } else { ++ throw new IllegalArgumentException("Payload may not be larger than " + MAX_PAYLOAD_SIZE + " bytes"); ++ } ++ }); ++ return buffer == null ? null : new net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket.QueryAnswerPayload(buffer); ++ // Paper end - MC Utils - default query payloads + } + + private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf buf) { +@@ -47,4 +57,21 @@ + public void handle(ServerLoginPacketListener listener) { + listener.handleCustomQueryPacket(this); + } ++ ++ // Paper start - MC Utils - default query payloads ++ public static final class QueryAnswerPayload implements CustomQueryAnswerPayload { ++ ++ public final FriendlyByteBuf buffer; ++ ++ public QueryAnswerPayload(final net.minecraft.network.FriendlyByteBuf buffer) { ++ this.buffer = buffer; ++ } ++ ++ @Override ++ public void write(final net.minecraft.network.FriendlyByteBuf buf) { ++ buf.writeBytes(this.buffer.copy()); ++ } ++ } ++ // Paper end - MC Utils - default query payloads ++ + } diff --git a/paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch b/paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch new file mode 100644 index 0000000000..d2519790c5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/net/minecraft/network/syncher/SynchedEntityData.java +@@ -50,8 +50,8 @@ + } + } + +- private SynchedEntityData.DataItem getItem(EntityDataAccessor key) { +- return this.itemsById[key.id()]; ++ public SynchedEntityData.DataItem getItem(EntityDataAccessor key) { // Paper - public ++ return (SynchedEntityData.DataItem) this.itemsById[key.id()]; // CraftBukkit - decompile error + } + + public T get(EntityDataAccessor data) { +@@ -74,6 +74,13 @@ + + } + ++ // CraftBukkit start - add method from above ++ public void markDirty(EntityDataAccessor datawatcherobject) { ++ this.getItem(datawatcherobject).setDirty(true); ++ this.isDirty = true; ++ } ++ // CraftBukkit end ++ + public boolean isDirty() { + return this.isDirty; + } +@@ -140,10 +147,24 @@ + if (!Objects.equals(from.serializer(), to.accessor.serializer())) { + throw new IllegalStateException(String.format(Locale.ROOT, "Invalid entity data item type for field %d on entity %s: old=%s(%s), new=%s(%s)", to.accessor.id(), this.entity, to.value, to.value.getClass(), from.value, from.value.getClass())); + } else { +- to.setValue(from.value); ++ to.setValue((T) from.value); // CraftBukkit - decompile error + } + } + ++ // Paper start ++ // We need to pack all as we cannot rely on "non default values" or "dirty" ones. ++ // Because these values can possibly be desynced on the client. ++ @Nullable ++ public List> packAll() { ++ final List> list = new ArrayList<>(); ++ for (final DataItem dataItem : this.itemsById) { ++ list.add(dataItem.value()); ++ } ++ ++ return list; ++ } ++ // Paper end ++ + public static class DataItem { + + final EntityDataAccessor accessor; diff --git a/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch new file mode 100644 index 0000000000..41773a3732 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch @@ -0,0 +1,79 @@ +--- a/net/minecraft/resources/RegistryDataLoader.java ++++ b/net/minecraft/resources/RegistryDataLoader.java +@@ -74,7 +74,7 @@ + + public class RegistryDataLoader { + private static final Logger LOGGER = LogUtils.getLogger(); +- private static final Comparator> ERROR_KEY_COMPARATOR = Comparator.comparing(ResourceKey::registry).thenComparing(ResourceKey::location); ++ private static final Comparator> ERROR_KEY_COMPARATOR = Comparator., ResourceLocation>comparing(ResourceKey::registry).thenComparing(ResourceKey::location); // Paper - decompile fix + private static final RegistrationInfo NETWORK_REGISTRATION_INFO = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); + private static final Function, RegistrationInfo> REGISTRATION_INFO_CACHE = Util.memoize(knownPacks -> { + Lifecycle lifecycle = knownPacks.map(KnownPack::isVanilla).map(vanilla -> Lifecycle.stable()).orElse(Lifecycle.experimental()); +@@ -238,13 +238,13 @@ + } + + private static void loadElementFromResource( +- WritableRegistry registry, Decoder decoder, RegistryOps ops, ResourceKey key, Resource resource, RegistrationInfo entryInfo ++ WritableRegistry registry, Decoder decoder, RegistryOps ops, ResourceKey key, Resource resource, RegistrationInfo entryInfo, io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions + ) throws IOException { + try (Reader reader = resource.openAsReader()) { + JsonElement jsonElement = JsonParser.parseReader(reader); + DataResult dataResult = decoder.parse(ops, jsonElement); + E object = dataResult.getOrThrow(); +- registry.register(key, object, entryInfo); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo, conversions); // Paper - register with listeners + } + } + +@@ -258,6 +258,7 @@ + FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key()); + RegistryOps registryOps = RegistryOps.create(JsonOps.INSTANCE, infoGetter); + ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(infoGetter); // Paper - create conversions + for (Entry entry : fileToIdConverter.listMatchingResources(resourceManager).entrySet()) { + ResourceLocation resourceLocation = entry.getKey(); + ResourceKey resourceKey = ResourceKey.create(registry.key(), fileToIdConverter.fileToId(resourceLocation)); +@@ -265,7 +266,7 @@ + RegistrationInfo registrationInfo = REGISTRATION_INFO_CACHE.apply(resource.knownPackInfo()); + + try { +- loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo); ++ loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo, conversions); // Paper - pass conversions + } catch (Exception var14) { + errors.put( + resourceKey, +@@ -274,7 +275,8 @@ + } + } + +- TagLoader.loadTagsForRegistry(resourceManager, registry); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), conversions); // Paper - run pre-freeze listeners ++ TagLoader.loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - tag lifecycle - add cause + } + + static void loadContentsFromNetwork( +@@ -291,6 +293,7 @@ + RegistryOps registryOps2 = RegistryOps.create(JsonOps.INSTANCE, infoGetter); + FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key()); + ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(infoGetter); // Paper - create conversions + for (RegistrySynchronization.PackedRegistryEntry packedRegistryEntry : networkedRegistryData.elements) { + ResourceKey resourceKey = ResourceKey.create(registry.key(), packedRegistryEntry.id()); + Optional optional = packedRegistryEntry.data(); +@@ -309,7 +312,7 @@ + + try { + Resource resource = factory.getResourceOrThrow(resourceLocation); +- loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO); ++ loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO, conversions); // Paper - pass conversions + } catch (Exception var17) { + loadingErrors.put(resourceKey, new IllegalStateException("Failed to parse local data", var17)); + } +@@ -349,6 +352,7 @@ + + RegistryDataLoader.Loader create(Lifecycle lifecycle, Map, Exception> errors) { + WritableRegistry writableRegistry = new MappedRegistry<>(this.key, lifecycle); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry + return new RegistryDataLoader.Loader<>(this, writableRegistry, errors); + } + diff --git a/paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch b/paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch new file mode 100644 index 0000000000..4f2bd280fb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/resources/ResourceLocation.java ++++ b/net/minecraft/resources/ResourceLocation.java +@@ -32,6 +32,7 @@ + public static final char NAMESPACE_SEPARATOR = ':'; + public static final String DEFAULT_NAMESPACE = "minecraft"; + public static final String REALMS_NAMESPACE = "realms"; ++ public static final String PAPER_NAMESPACE = "paper"; // Paper + private final String namespace; + private final String path; + +@@ -40,6 +41,13 @@ + + assert isValidPath(path); + ++ // Paper start - Validate ResourceLocation ++ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short) ++ final String resourceLocation = namespace + ":" + path; ++ if (resourceLocation.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(resourceLocation) > 2 * Short.MAX_VALUE + 1) { ++ throw new ResourceLocationException("Resource location too long: " + resourceLocation); ++ } ++ // Paper end - Validate ResourceLocation + this.namespace = namespace; + this.path = path; + } +@@ -246,7 +254,7 @@ + + private static String assertValidNamespace(String namespace, String path) { + if (!isValidNamespace(namespace)) { +- throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + namespace + ":" + path); ++ throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + org.apache.commons.lang3.StringUtils.normalizeSpace(namespace) + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(path)); // Paper - Sanitize ResourceLocation error logging + } else { + return namespace; + } +@@ -267,7 +275,7 @@ + + private static String assertValidPath(String namespace, String path) { + if (!isValidPath(path)) { +- throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + path); ++ throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(path)); // Paper - Sanitize ResourceLocation error logging + } else { + return path; + } diff --git a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch new file mode 100644 index 0000000000..efce551cae --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch @@ -0,0 +1,129 @@ +--- a/net/minecraft/server/Bootstrap.java ++++ b/net/minecraft/server/Bootstrap.java +@@ -17,6 +17,9 @@ + import net.minecraft.core.dispenser.DispenseItemBehavior; + import net.minecraft.core.registries.BuiltInRegistries; + import net.minecraft.locale.Language; ++import net.minecraft.util.datafix.fixes.BlockStateData; ++import net.minecraft.util.datafix.fixes.ItemIdFix; ++import net.minecraft.util.datafix.fixes.ItemSpawnEggFix; + import net.minecraft.world.effect.MobEffect; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.ai.attributes.Attribute; +@@ -30,7 +33,8 @@ + import net.minecraft.world.level.block.state.BlockBehaviour; + import org.slf4j.Logger; + +-@SuppressForbidden(a = "System.out setup") ++@SuppressForbidden(reason = "System.out setup") ++// CraftBukkit end + public class Bootstrap { + + public static final PrintStream STDOUT = System.out; +@@ -42,9 +46,27 @@ + + public static void bootStrap() { + if (!Bootstrap.isBootstrapped) { ++ // CraftBukkit start ++ /*String name = Bootstrap.class.getSimpleName(); // Paper ++ switch (name) { ++ case "DispenserRegistry": ++ break; ++ case "Bootstrap": ++ System.err.println("***************************************************************************"); ++ System.err.println("*** WARNING: This server jar may only be used for development purposes. ***"); ++ System.err.println("***************************************************************************"); ++ break; ++ default: ++ System.err.println("**********************************************************************"); ++ System.err.println("*** WARNING: This server jar is unsupported, use at your own risk. ***"); ++ System.err.println("**********************************************************************"); ++ break; ++ }*/ // Paper ++ // CraftBukkit end + Bootstrap.isBootstrapped = true; + Instant instant = Instant.now(); + ++ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping + if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) { + throw new IllegalStateException("Unable to load registries"); + } else { +@@ -56,11 +78,77 @@ + EntitySelectorOptions.bootStrap(); + DispenseItemBehavior.bootStrap(); + CauldronInteraction.bootStrap(); +- BuiltInRegistries.bootStrap(); ++ // Paper start ++ BuiltInRegistries.bootStrap(() -> { ++ }); ++ // Paper end + CreativeModeTabs.validate(); + Bootstrap.wrapStreams(); + Bootstrap.bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis()); + } ++ // CraftBukkit start - easier than fixing the decompile ++ BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}"); ++ BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}"); ++ BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}"); ++ BlockStateData.register(1011, "{Name:'minecraft:oak_sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}"); ++ BlockStateData.register(1012, "{Name:'minecraft:oak_sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}"); ++ BlockStateData.register(1013, "{Name:'minecraft:oak_sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}"); ++ BlockStateData.register(1014, "{Name:'minecraft:oak_sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}"); ++ BlockStateData.register(1015, "{Name:'minecraft:oak_sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}"); ++ BlockStateData.register(1016, "{Name:'minecraft:oak_sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}"); ++ BlockStateData.register(1017, "{Name:'minecraft:oak_sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}"); ++ BlockStateData.register(1018, "{Name:'minecraft:oak_sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}"); ++ BlockStateData.register(1019, "{Name:'minecraft:oak_sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}"); ++ BlockStateData.register(1020, "{Name:'minecraft:oak_sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}"); ++ BlockStateData.register(1021, "{Name:'minecraft:oak_sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}"); ++ BlockStateData.register(1022, "{Name:'minecraft:oak_sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}"); ++ BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}"); ++ ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign"); ++ ++ BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"}); ++ ++ ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard"); ++ ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals"); ++ ItemIdFix.ITEM_NAMES.put(411, "minecraft:rabbit"); ++ ItemIdFix.ITEM_NAMES.put(412, "minecraft:cooked_rabbit"); ++ ItemIdFix.ITEM_NAMES.put(413, "minecraft:rabbit_stew"); ++ ItemIdFix.ITEM_NAMES.put(414, "minecraft:rabbit_foot"); ++ ItemIdFix.ITEM_NAMES.put(415, "minecraft:rabbit_hide"); ++ ItemIdFix.ITEM_NAMES.put(416, "minecraft:armor_stand"); ++ ++ ItemIdFix.ITEM_NAMES.put(423, "minecraft:mutton"); ++ ItemIdFix.ITEM_NAMES.put(424, "minecraft:cooked_mutton"); ++ ItemIdFix.ITEM_NAMES.put(425, "minecraft:banner"); ++ ItemIdFix.ITEM_NAMES.put(426, "minecraft:end_crystal"); ++ ItemIdFix.ITEM_NAMES.put(427, "minecraft:spruce_door"); ++ ItemIdFix.ITEM_NAMES.put(428, "minecraft:birch_door"); ++ ItemIdFix.ITEM_NAMES.put(429, "minecraft:jungle_door"); ++ ItemIdFix.ITEM_NAMES.put(430, "minecraft:acacia_door"); ++ ItemIdFix.ITEM_NAMES.put(431, "minecraft:dark_oak_door"); ++ ItemIdFix.ITEM_NAMES.put(432, "minecraft:chorus_fruit"); ++ ItemIdFix.ITEM_NAMES.put(433, "minecraft:chorus_fruit_popped"); ++ ItemIdFix.ITEM_NAMES.put(434, "minecraft:beetroot"); ++ ItemIdFix.ITEM_NAMES.put(435, "minecraft:beetroot_seeds"); ++ ItemIdFix.ITEM_NAMES.put(436, "minecraft:beetroot_soup"); ++ ItemIdFix.ITEM_NAMES.put(437, "minecraft:dragon_breath"); ++ ItemIdFix.ITEM_NAMES.put(438, "minecraft:splash_potion"); ++ ItemIdFix.ITEM_NAMES.put(439, "minecraft:spectral_arrow"); ++ ItemIdFix.ITEM_NAMES.put(440, "minecraft:tipped_arrow"); ++ ItemIdFix.ITEM_NAMES.put(441, "minecraft:lingering_potion"); ++ ItemIdFix.ITEM_NAMES.put(442, "minecraft:shield"); ++ ItemIdFix.ITEM_NAMES.put(443, "minecraft:elytra"); ++ ItemIdFix.ITEM_NAMES.put(444, "minecraft:spruce_boat"); ++ ItemIdFix.ITEM_NAMES.put(445, "minecraft:birch_boat"); ++ ItemIdFix.ITEM_NAMES.put(446, "minecraft:jungle_boat"); ++ ItemIdFix.ITEM_NAMES.put(447, "minecraft:acacia_boat"); ++ ItemIdFix.ITEM_NAMES.put(448, "minecraft:dark_oak_boat"); ++ ItemIdFix.ITEM_NAMES.put(449, "minecraft:totem_of_undying"); ++ ItemIdFix.ITEM_NAMES.put(450, "minecraft:shulker_shell"); ++ ItemIdFix.ITEM_NAMES.put(452, "minecraft:iron_nugget"); ++ ItemIdFix.ITEM_NAMES.put(453, "minecraft:knowledge_book"); ++ ++ ItemSpawnEggFix.ID_TO_ENTITY[23] = "Arrow"; ++ // CraftBukkit end + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/Main.java.patch b/paper-server/patches/sources/net/minecraft/server/Main.java.patch new file mode 100644 index 0000000000..a9ae49e37f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/Main.java.patch @@ -0,0 +1,290 @@ +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -38,6 +38,7 @@ + import net.minecraft.server.dedicated.DedicatedServerProperties; + import net.minecraft.server.dedicated.DedicatedServerSettings; + import net.minecraft.server.level.progress.LoggerChunkProgressListener; ++import net.minecraft.server.packs.PackType; + import net.minecraft.server.packs.repository.PackRepository; + import net.minecraft.server.packs.repository.ServerPacksSource; + import net.minecraft.util.Mth; +@@ -55,22 +56,31 @@ + import net.minecraft.world.level.levelgen.WorldOptions; + import net.minecraft.world.level.levelgen.presets.WorldPresets; + import net.minecraft.world.level.storage.LevelDataAndDimensions; ++import net.minecraft.world.level.storage.LevelResource; + import net.minecraft.world.level.storage.LevelStorageSource; + import net.minecraft.world.level.storage.LevelSummary; + import net.minecraft.world.level.storage.PrimaryLevelData; +-import net.minecraft.world.level.storage.WorldData; + import org.slf4j.Logger; + ++// CraftBukkit start ++import com.google.common.base.Charsets; ++import java.io.InputStreamReader; ++import java.util.concurrent.atomic.AtomicReference; ++import net.minecraft.SharedConstants; ++import org.bukkit.configuration.file.YamlConfiguration; ++// CraftBukkit end ++ + public class Main { + + private static final Logger LOGGER = LogUtils.getLogger(); + + public Main() {} + +- @SuppressForbidden(a = "System.out needed before bootstrap") ++ @SuppressForbidden(reason = "System.out needed before bootstrap") // CraftBukkit - decompile error + @DontObfuscate +- public static void main(String[] args) { ++ public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring) + SharedConstants.tryDetectVersion(); ++ /* CraftBukkit start - Replace everything + OptionParser optionparser = new OptionParser(); + OptionSpec optionspec = optionparser.accepts("nogui"); + OptionSpec optionspec1 = optionparser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits"); +@@ -90,50 +100,104 @@ + OptionSpec optionspec15 = optionparser.nonOptions(); + + try { +- OptionSet optionset = optionparser.parse(args); ++ OptionSet optionset = optionparser.parse(astring); + + if (optionset.has(optionspec8)) { + optionparser.printHelpOn(System.err); + return; + } ++ */ // CraftBukkit end + +- Path path = (Path) optionset.valueOf(optionspec14); ++ try { + ++ Path path = (Path) optionset.valueOf("pidFile"); // CraftBukkit ++ + if (path != null) { + Main.writePidFile(path); + } + + CrashReport.preload(); +- if (optionset.has(optionspec13)) { ++ if (optionset.has("jfrProfile")) { // CraftBukkit + JvmProfiler.INSTANCE.start(Environment.SERVER); + } + ++ io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper + Bootstrap.bootStrap(); + Bootstrap.validate(); + Util.startTimerHackThread(); + Path path1 = Paths.get("server.properties"); +- DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(path1); ++ DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support + + dedicatedserversettings.forceSave(); + RegionFileVersion.configure(dedicatedserversettings.getProperties().regionFileComression); + Path path2 = Paths.get("eula.txt"); + Eula eula = new Eula(path2); ++ // Paper start - load config files early for access below if needed ++ org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("bukkit-settings")); ++ org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("spigot-settings")); ++ // Paper end - load config files early for access below if needed + +- if (optionset.has(optionspec1)) { ++ if (optionset.has("initSettings")) { // CraftBukkit ++ // CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present ++ File configFile = (File) optionset.valueOf("bukkit-settings"); ++ YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile); ++ configuration.options().copyDefaults(true); ++ configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8))); ++ configuration.save(configFile); ++ ++ File commandFile = (File) optionset.valueOf("commands-settings"); ++ YamlConfiguration commandsConfiguration = YamlConfiguration.loadConfiguration(commandFile); ++ commandsConfiguration.options().copyDefaults(true); ++ commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); ++ commandsConfiguration.save(commandFile); ++ // CraftBukkit end + Main.LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath()); + return; + } + +- if (!eula.hasAgreedToEULA()) { ++ // Spigot Start ++ boolean eulaAgreed = Boolean.getBoolean( "com.mojang.eula.agree" ); ++ if ( eulaAgreed ) ++ { ++ System.err.println( "You have used the Spigot command line EULA agreement flag." ); ++ System.err.println( "By using this setting you are indicating your agreement to Mojang's EULA (https://account.mojang.com/documents/minecraft_eula)." ); ++ System.err.println( "If you do not agree to the above EULA please stop your server and remove this flag immediately." ); ++ } ++ // Spigot End ++ if (!eula.hasAgreedToEULA() && !eulaAgreed) { // Spigot + Main.LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); + return; + } + +- File file = new File((String) optionset.valueOf(optionspec9)); +- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file); +- String s = (String) Optional.ofNullable((String) optionset.valueOf(optionspec10)).orElse(dedicatedserversettings.getProperties().levelName); ++ // Paper start - Detect headless JRE ++ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); ++ if (awtException != null) { ++ Main.LOGGER.error("You are using a headless JRE distribution."); ++ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function."); ++ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install"); ++ Main.LOGGER.error(""); ++ Main.LOGGER.error(awtException); ++ return; ++ } ++ // Paper end - Detect headless JRE ++ ++ org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init ++ // Paper start - fix SPIGOT-5824 ++ File file; ++ File userCacheFile = new File(Services.USERID_CACHE_FILE); ++ if (optionset.has("universe")) { ++ file = (File) optionset.valueOf("universe"); // CraftBukkit ++ userCacheFile = new File(file, Services.USERID_CACHE_FILE); ++ } else { ++ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); ++ } ++ // Paper end - fix SPIGOT-5824 ++ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container ++ // CraftBukkit start ++ String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); +- LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s); ++ LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s, LevelStem.OVERWORLD); ++ // CraftBukkit end + Dynamic dynamic; + + if (convertable_conversionsession.hasWorldData()) { +@@ -174,13 +238,31 @@ + } + + Dynamic dynamic1 = dynamic; +- boolean flag = optionset.has(optionspec7); ++ boolean flag = optionset.has("safeMode"); // CraftBukkit + + if (flag) { + Main.LOGGER.warn("Safe mode active, only vanilla datapack will be loaded"); + } + + PackRepository resourcepackrepository = ServerPacksSource.createPackRepository(convertable_conversionsession); ++ // CraftBukkit start ++ File bukkitDataPackFolder = new File(convertable_conversionsession.getLevelPath(LevelResource.DATAPACK_DIR).toFile(), "bukkit"); ++ if (!bukkitDataPackFolder.exists()) { ++ bukkitDataPackFolder.mkdirs(); ++ } ++ File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta"); ++ try { ++ com.google.common.io.Files.write("{\n" ++ + " \"pack\": {\n" ++ + " \"description\": \"Data pack for resources provided by Bukkit plugins\",\n" ++ + " \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n" ++ + " }\n" ++ + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException("Could not initialize Bukkit datapack", ex); ++ } ++ AtomicReference worldLoader = new AtomicReference<>(); ++ // CraftBukkit end + + WorldStem worldstem; + +@@ -189,6 +271,7 @@ + + worldstem = (WorldStem) Util.blockUntilDone((executor) -> { + return WorldLoader.load(worldloader_c, (worldloader_a) -> { ++ worldLoader.set(worldloader_a); // CraftBukkit + Registry iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); + + if (dynamic1 != null) { +@@ -201,7 +284,7 @@ + WorldOptions worldoptions; + WorldDimensions worlddimensions; + +- if (optionset.has(optionspec2)) { ++ if (optionset.has("demo")) { // CraftBukkit + worldsettings = MinecraftServer.DEMO_SETTINGS; + worldoptions = WorldOptions.DEMO_OPTIONS; + worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen()); +@@ -209,7 +292,7 @@ + DedicatedServerProperties dedicatedserverproperties = dedicatedserversettings.getProperties(); + + worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration()); +- worldoptions = optionset.has(optionspec3) ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; ++ worldoptions = optionset.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; // CraftBukkit + worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen()); + } + +@@ -225,32 +308,47 @@ + return; + } + +- RegistryAccess.Frozen iregistrycustom_dimension = worldstem.registries().compositeAccess(); ++ /* ++ IRegistryCustom.Dimension iregistrycustom_dimension = worldstem.registries().compositeAccess(); + boolean flag1 = optionset.has(optionspec6); + + if (optionset.has(optionspec4) || flag1) { +- Main.forceUpgrade(convertable_conversionsession, DataFixers.getDataFixer(), optionset.has(optionspec5), () -> { ++ forceUpgrade(convertable_conversionsession, DataConverterRegistry.getDataFixer(), optionset.has(optionspec5), () -> { + return true; + }, iregistrycustom_dimension, flag1); + } + +- WorldData savedata = worldstem.worldData(); ++ SaveData savedata = worldstem.worldData(); + + convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata); ++ */ + final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> { +- DedicatedServer dedicatedserver1 = new DedicatedServer(thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius); ++ DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius); + ++ /* + dedicatedserver1.setPort((Integer) optionset.valueOf(optionspec11)); +- dedicatedserver1.setDemo(optionset.has(optionspec2)); ++ */ ++ dedicatedserver1.setDemo(optionset.has("demo")); // Paper ++ /* + dedicatedserver1.setId((String) optionset.valueOf(optionspec12)); +- boolean flag2 = !optionset.has(optionspec) && !optionset.valuesOf(optionspec15).contains("nogui"); ++ */ ++ boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); + ++ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui + if (flag2 && !GraphicsEnvironment.isHeadless()) { + dedicatedserver1.showGui(); + } + ++ if (optionset.has("port")) { ++ int port = (Integer) optionset.valueOf("port"); ++ if (port > 0) { ++ dedicatedserver1.setPort(port); ++ } ++ } ++ + return dedicatedserver1; + }); ++ /* CraftBukkit start + Thread thread = new Thread("Server Shutdown Thread") { + public void run() { + dedicatedserver.halt(true); +@@ -259,6 +357,7 @@ + + thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(Main.LOGGER)); + Runtime.getRuntime().addShutdownHook(thread); ++ */ // CraftBukkit end + } catch (Exception exception1) { + Main.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", exception1); + } +@@ -295,7 +394,7 @@ + } + + public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, RegistryAccess dynamicRegistryManager, boolean recreateRegionFiles) { +- Main.LOGGER.info("Forcing world upgrade!"); ++ Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dynamicRegistryManager, eraseCache, recreateRegionFiles); + + try { diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch new file mode 100644 index 0000000000..b2aec783a8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -0,0 +1,1501 @@ +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -3,6 +3,9 @@ + import com.google.common.base.Preconditions; + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; ++import co.aikar.timings.Timings; ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import com.google.common.base.Stopwatch; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -45,7 +48,6 @@ + import java.util.UUID; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; +-import java.util.concurrent.RejectedExecutionException; + import java.util.concurrent.atomic.AtomicReference; + import java.util.concurrent.locks.LockSupport; + import java.util.function.BooleanSupplier; +@@ -84,17 +86,6 @@ + import net.minecraft.obfuscate.DontObfuscate; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; +-import net.minecraft.server.bossevents.CustomBossEvents; +-import net.minecraft.server.level.DemoMode; +-import net.minecraft.server.level.PlayerRespawnLogic; +-import net.minecraft.server.level.ServerChunkCache; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.server.level.ServerPlayerGameMode; +-import net.minecraft.server.level.progress.ChunkProgressListener; +-import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +-import net.minecraft.server.network.ServerConnectionListener; +-import net.minecraft.server.network.TextFilter; + import net.minecraft.server.packs.PackType; + import net.minecraft.server.packs.repository.Pack; + import net.minecraft.server.packs.repository.PackRepository; +@@ -116,6 +107,7 @@ + import net.minecraft.util.RandomSource; + import net.minecraft.util.SignatureValidator; + import net.minecraft.util.TimeUtil; ++import net.minecraft.util.datafix.DataFixers; + import net.minecraft.util.debugchart.RemoteDebugSampleType; + import net.minecraft.util.debugchart.SampleLogger; + import net.minecraft.util.debugchart.TpsDebugDimensions; +@@ -156,37 +148,71 @@ + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.entity.FuelValues; +-import net.minecraft.world.level.border.BorderChangeListener; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.chunk.storage.ChunkIOErrorReporter; + import net.minecraft.world.level.chunk.storage.RegionStorageInfo; + import net.minecraft.world.level.dimension.LevelStem; +-import net.minecraft.world.level.levelgen.Heightmap; +-import net.minecraft.world.level.levelgen.PatrolSpawner; +-import net.minecraft.world.level.levelgen.PhantomSpawner; + import net.minecraft.world.level.levelgen.WorldOptions; + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; ++import net.minecraft.world.level.storage.WorldData; ++import org.slf4j.Logger; ++ ++// CraftBukkit start ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.Lifecycle; ++import java.io.File; ++import java.util.Random; ++// import jline.console.ConsoleReader; // Paper ++import joptsimple.OptionSet; ++import net.minecraft.nbt.NbtException; ++import net.minecraft.nbt.ReportedNbtException; ++import net.minecraft.server.bossevents.CustomBossEvents; ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecraft.server.dedicated.DedicatedServerProperties; ++import net.minecraft.server.level.DemoMode; ++import net.minecraft.server.level.PlayerRespawnLogic; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.server.level.progress.ChunkProgressListener; ++import net.minecraft.server.level.progress.ChunkProgressListenerFactory; ++import net.minecraft.server.network.ServerConnectionListener; ++import net.minecraft.server.network.TextFilter; ++import net.minecraft.world.level.levelgen.Heightmap; ++import net.minecraft.world.level.levelgen.PatrolSpawner; ++import net.minecraft.world.level.levelgen.PhantomSpawner; ++import net.minecraft.world.level.levelgen.WorldDimensions; ++import net.minecraft.world.level.levelgen.presets.WorldPresets; + import net.minecraft.world.level.storage.CommandStorage; +-import net.minecraft.world.level.storage.DerivedLevelData; + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelData; ++import net.minecraft.world.level.storage.LevelDataAndDimensions; + import net.minecraft.world.level.storage.LevelResource; + import net.minecraft.world.level.storage.LevelStorageSource; ++import net.minecraft.world.level.storage.LevelSummary; + import net.minecraft.world.level.storage.PlayerDataStorage; ++import net.minecraft.world.level.storage.PrimaryLevelData; + import net.minecraft.world.level.storage.ServerLevelData; +-import net.minecraft.world.level.storage.WorldData; ++import net.minecraft.world.level.validation.ContentValidationException; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; +-import org.slf4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.bukkit.event.server.ServerLoadEvent; ++// CraftBukkit end + ++ + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource { + ++ private static MinecraftServer SERVER; // Paper + public static final Logger LOGGER = LogUtils.getLogger(); ++ public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper + public static final String VANILLA_BRAND = "vanilla"; + private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F; + private static final int TICK_STATS_SPAN = 100; +- private static final long OVERLOADED_THRESHOLD_NANOS = 20L * TimeUtil.NANOSECONDS_PER_SECOND / 20L; ++ private static final long OVERLOADED_THRESHOLD_NANOS = 30L * TimeUtil.NANOSECONDS_PER_SECOND / 20L; // CraftBukkit + private static final int OVERLOADED_TICKS_THRESHOLD = 20; + private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND; + private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100; +@@ -224,6 +250,7 @@ + private Map, ServerLevel> levels; + private PlayerList playerList; + private volatile boolean running; ++ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart + private boolean stopped; + private int tickCount; + private int ticksUntilAutosave; +@@ -232,11 +259,15 @@ + private boolean preventProxyConnections; + private boolean pvp; + private boolean allowFlight; +- @Nullable +- private String motd; ++ private net.kyori.adventure.text.Component motd; // Paper - Adventure + private int playerIdleTimeout; + private final long[] tickTimesNanos; + private long aggregatedTickTimesNanos; ++ // Paper start - Add tick times API and /mspt command ++ public final TickTimes tickTimes5s = new TickTimes(100); ++ public final TickTimes tickTimes10s = new TickTimes(200); ++ public final TickTimes tickTimes60s = new TickTimes(1200); ++ // Paper end - Add tick times API and /mspt command + @Nullable + private KeyPair keyPair; + @Nullable +@@ -277,6 +308,28 @@ + private final SuppressedExceptionCollector suppressedExceptions; + private final DiscontinuousFrame tickFrame; + ++ // CraftBukkit start ++ public final WorldLoader.DataLoadContext worldLoader; ++ public org.bukkit.craftbukkit.CraftServer server; ++ public OptionSet options; ++ public org.bukkit.command.ConsoleCommandSender console; ++ public static int currentTick; // Paper - improve tick loop ++ public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); ++ public int autosavePeriod; ++ // Paper - don't store the vanilla dispatcher ++ private boolean forceTicks; ++ // CraftBukkit end ++ // Spigot start ++ public static final int TPS = 20; ++ public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS; ++ private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop ++ @Deprecated(forRemoval = true) // Paper ++ public final double[] recentTps = new double[ 3 ]; ++ // Spigot end ++ public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files ++ public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked ++ private final Set pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping ++ + public static S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new Thread(() -> { +@@ -286,19 +339,21 @@ + thread.setUncaughtExceptionHandler((thread1, throwable) -> { + MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); + }); ++ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority + if (Runtime.getRuntime().availableProcessors() > 4) { + thread.setPriority(8); + } + +- S s0 = (MinecraftServer) serverFactory.apply(thread); ++ S s0 = serverFactory.apply(thread); // CraftBukkit - decompile error + + atomicreference.set(s0); + thread.start(); + return s0; + } + +- public MinecraftServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, Proxy proxy, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) { ++ public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) { + super("Server"); ++ SERVER = this; // Paper - better singleton + this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; + this.onMetricsRecordingStopped = (methodprofilerresults) -> { + this.stopRecordingMetrics(); +@@ -319,36 +374,67 @@ + this.scoreboard = new ServerScoreboard(this); + this.customBossEvents = new CustomBossEvents(); + this.suppressedExceptions = new SuppressedExceptionCollector(); +- this.registries = saveLoader.registries(); +- this.worldData = saveLoader.worldData(); +- if (!this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { ++ this.registries = worldstem.registries(); ++ this.worldData = worldstem.worldData(); ++ if (false && !this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { // CraftBukkit - initialised later + throw new IllegalStateException("Missing Overworld dimension data"); + } else { + this.proxy = proxy; +- this.packRepository = dataPackManager; +- this.resources = new MinecraftServer.ReloadableResources(saveLoader.resourceManager(), saveLoader.dataPackResources()); +- this.services = apiServices; +- if (apiServices.profileCache() != null) { +- apiServices.profileCache().setExecutor(this); ++ this.packRepository = resourcepackrepository; ++ this.resources = new MinecraftServer.ReloadableResources(worldstem.resourceManager(), worldstem.dataPackResources()); ++ this.services = services; ++ if (services.profileCache() != null) { ++ services.profileCache().setExecutor(this); + } + +- this.connection = new ServerConnectionListener(this); ++ // this.connection = new ServerConnection(this); // Spigot + this.tickRateManager = new ServerTickRateManager(this); +- this.progressListenerFactory = worldGenerationProgressListenerFactory; +- this.storageSource = session; +- this.playerDataStorage = session.createPlayerStorage(); +- this.fixerUpper = dataFixer; ++ this.progressListenerFactory = worldloadlistenerfactory; ++ this.storageSource = convertable_conversionsession; ++ this.playerDataStorage = convertable_conversionsession.createPlayerStorage(); ++ this.fixerUpper = datafixer; + this.functionManager = new ServerFunctionManager(this, this.resources.managers.getFunctionLibrary()); + HolderGetter holdergetter = this.registries.compositeAccess().lookupOrThrow(Registries.BLOCK).filterFeatures(this.worldData.enabledFeatures()); + +- this.structureTemplateManager = new StructureTemplateManager(saveLoader.resourceManager(), session, dataFixer, holdergetter); +- this.serverThread = serverThread; ++ this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer, holdergetter); ++ this.serverThread = thread; + this.executor = Util.backgroundExecutor(); + this.potionBrewing = PotionBrewing.bootstrap(this.worldData.enabledFeatures()); + this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures()); + this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures()); + this.tickFrame = TracyClient.createDiscontinuousFrame("Server Tick"); + } ++ // CraftBukkit start ++ this.options = options; ++ this.worldLoader = worldLoader; ++ // Paper start - Handled by TerminalConsoleAppender ++ // 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, System.out); ++ this.reader.setExpandEvents(false); // Avoid parsing exceptions for uncommonly used event designators ++ } catch (Throwable e) { ++ try { ++ // Try again with jline disabled for Windows users without C++ 2008 Redistributable ++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal"); ++ System.setProperty("user.language", "en"); ++ Main.useJline = false; ++ this.reader = new ConsoleReader(System.in, System.out); ++ this.reader.setExpandEvents(false); ++ } catch (IOException ex) { ++ MinecraftServer.LOGGER.warn((String) null, ex); ++ } ++ } ++ */ ++ // Paper end ++ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this)); ++ // CraftBukkit end ++ this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files + } + + private void readScoreboard(DimensionDataStorage persistentStateManager) { +@@ -357,7 +443,7 @@ + + protected abstract boolean initServer() throws IOException; + +- protected void loadLevel() { ++ protected void loadLevel(String s) { // CraftBukkit + if (!JvmProfiler.INSTANCE.isRunning()) { + ; + } +@@ -365,12 +451,8 @@ + boolean flag = false; + ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted(); + +- this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); +- ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)); ++ this.loadWorld0(s); // CraftBukkit + +- this.createLevels(worldloadlistener); +- this.forceDifficulty(); +- this.prepareLevels(worldloadlistener); + if (profiledduration != null) { + profiledduration.finish(true); + } +@@ -387,23 +469,246 @@ + + protected void forceDifficulty() {} + +- protected void createLevels(ChunkProgressListener worldGenerationProgressListener) { +- ServerLevelData iworlddataserver = this.worldData.overworldData(); +- boolean flag = this.worldData.isDebugWorld(); +- Registry iregistry = this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM); +- WorldOptions worldoptions = this.worldData.worldGenOptions(); +- long i = worldoptions.seed(); +- long j = BiomeManager.obfuscateSeed(i); +- List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); +- LevelStem worlddimension = (LevelStem) iregistry.getValue(LevelStem.OVERWORLD); +- ServerLevel worldserver = new ServerLevel(this, this.executor, this.storageSource, iworlddataserver, Level.OVERWORLD, worlddimension, worldGenerationProgressListener, flag, j, list, true, (RandomSequences) null); ++ // CraftBukkit start ++ private void loadWorld0(String s) { ++ LevelStorageSource.LevelStorageAccess worldSession = this.storageSource; + +- this.levels.put(Level.OVERWORLD, worldserver); +- DimensionDataStorage worldpersistentdata = worldserver.getDataStorage(); ++ RegistryAccess.Frozen iregistrycustom_dimension = this.registries.compositeAccess(); ++ Registry dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM); ++ for (LevelStem worldDimension : dimensions) { ++ ResourceKey dimensionKey = dimensions.getResourceKey(worldDimension).get(); + +- this.readScoreboard(worldpersistentdata); +- this.commandStorage = new CommandStorage(worldpersistentdata); ++ ServerLevel world; ++ int dimension = 0; ++ ++ if (dimensionKey == LevelStem.NETHER) { ++ if (this.server.getAllowNether()) { ++ dimension = -1; ++ } else { ++ continue; ++ } ++ } else if (dimensionKey == LevelStem.END) { ++ if (this.server.getAllowEnd()) { ++ dimension = 1; ++ } else { ++ continue; ++ } ++ } else if (dimensionKey != LevelStem.OVERWORLD) { ++ dimension = -999; ++ } ++ ++ String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT); ++ String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType; ++ if (dimension != 0) { ++ File newWorld = LevelStorageSource.getStorageFolder(new File(name).toPath(), dimensionKey).toFile(); ++ File oldWorld = LevelStorageSource.getStorageFolder(new File(s).toPath(), dimensionKey).toFile(); ++ File oldLevelDat = new File(new File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't ++ ++ if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) { ++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----"); ++ MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly."); ++ MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future."); ++ MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "..."); ++ ++ if (newWorld.exists()) { ++ MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!"); ++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----"); ++ } else if (newWorld.getParentFile().mkdirs()) { ++ if (oldWorld.renameTo(newWorld)) { ++ MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld); ++ // Migrate world data too. ++ try { ++ com.google.common.io.Files.copy(oldLevelDat, new File(new File(name), "level.dat")); ++ org.apache.commons.io.FileUtils.copyDirectory(new File(new File(s), "data"), new File(new File(name), "data")); ++ } catch (IOException exception) { ++ MinecraftServer.LOGGER.warn("Unable to migrate world data."); ++ } ++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----"); ++ } else { ++ MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!"); ++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----"); ++ } ++ } else { ++ MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!"); ++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----"); ++ } ++ } ++ ++ try { ++ worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey); ++ } catch (IOException | ContentValidationException ex) { ++ throw new RuntimeException(ex); ++ } ++ } ++ ++ Dynamic dynamic; ++ if (worldSession.hasWorldData()) { ++ LevelSummary worldinfo; ++ ++ try { ++ dynamic = worldSession.getDataTag(); ++ worldinfo = worldSession.getSummary(dynamic); ++ } catch (NbtException | ReportedNbtException | IOException ioexception) { ++ LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory(); ++ ++ MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception); ++ MinecraftServer.LOGGER.info("Attempting to use fallback"); ++ ++ try { ++ dynamic = worldSession.getDataTagFallback(); ++ worldinfo = worldSession.getSummary(dynamic); ++ } catch (NbtException | ReportedNbtException | IOException ioexception1) { ++ MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1); ++ MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile()); ++ return; ++ } ++ ++ worldSession.restoreLevelDataFromOld(); ++ } ++ ++ if (worldinfo.requiresManualConversion()) { ++ MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted"); ++ return; ++ } ++ ++ if (!worldinfo.isCompatible()) { ++ MinecraftServer.LOGGER.info("This world was created by an incompatible version."); ++ return; ++ } ++ } else { ++ dynamic = null; ++ } ++ ++ org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name); ++ org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name); ++ ++ PrimaryLevelData worlddata; ++ WorldLoader.DataLoadContext worldloader_a = this.worldLoader; ++ Registry iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); ++ if (dynamic != null) { ++ LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen()); ++ ++ worlddata = (PrimaryLevelData) leveldataanddimensions.worldData(); ++ } else { ++ LevelSettings worldsettings; ++ WorldOptions worldoptions; ++ WorldDimensions worlddimensions; ++ ++ if (this.isDemo()) { ++ worldsettings = MinecraftServer.DEMO_SETTINGS; ++ worldoptions = WorldOptions.DEMO_OPTIONS; ++ worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen()); ++ } else { ++ DedicatedServerProperties dedicatedserverproperties = ((DedicatedServer) this).getProperties(); ++ ++ worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration()); ++ worldoptions = this.options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; ++ worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen()); ++ } ++ ++ WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry); ++ Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle()); ++ ++ worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle); ++ } ++ 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) ++ if (this.options.has("forceUpgrade")) { ++ net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> { ++ return true; ++ }, iregistrycustom_dimension, this.options.has("recreateRegionFiles")); ++ } ++ ++ PrimaryLevelData iworlddataserver = worlddata; ++ boolean flag = worlddata.isDebugWorld(); ++ WorldOptions worldoptions = worlddata.worldGenOptions(); ++ long i = worldoptions.seed(); ++ long j = BiomeManager.obfuscateSeed(i); ++ List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); ++ LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey); ++ ++ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo ++ if (biomeProvider == null && gen != null) { ++ biomeProvider = gen.getDefaultBiomeProvider(worldInfo); ++ } ++ ++ ResourceKey worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location()); ++ ++ if (dimensionKey == LevelStem.OVERWORLD) { ++ this.worldData = worlddata; ++ this.worldData.setGameType(((DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init ++ ++ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)); ++ ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ DimensionDataStorage worldpersistentdata = world.getDataStorage(); ++ this.readScoreboard(worldpersistentdata); ++ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard()); ++ this.commandStorage = new CommandStorage(worldpersistentdata); ++ } else { ++ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)); ++ // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future. ++ final List spawners; ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { ++ spawners = list; ++ } else { ++ spawners = Collections.emptyList(); ++ } ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ // Paper end - option to use the dimension_type to check if spawners should be added ++ } ++ ++ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); ++ this.addLevel(world); // Paper - Put world into worldlist before initing the world; move up ++ this.initWorld(world, worlddata, this.worldData, worldoptions); ++ ++ // Paper - Put world into worldlist before initing the world; move up ++ this.getPlayerList().addWorldborderListener(world); ++ ++ if (worlddata.getCustomBossEvents() != null) { ++ this.getCustomBossEvents().load(worlddata.getCustomBossEvents(), this.registryAccess()); ++ } ++ } ++ this.forceDifficulty(); ++ for (ServerLevel worldserver : this.getAllLevels()) { ++ this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver); ++ worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API ++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld())); ++ } ++ ++ // Paper start - Configurable player collision; Handle collideRule team for player collision toggle ++ final ServerScoreboard scoreboard = this.getScoreboard(); ++ final java.util.Collection toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList()); ++ for (String teamName : toRemove) { ++ scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves ++ } ++ ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { ++ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16); ++ net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName); ++ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all ++ } ++ // Paper end - Configurable player collision ++ ++ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); ++ this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark ++ this.server.spark.enableAfterPlugins(this.server); // Paper - spark ++ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins ++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins ++ ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands(); ++ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); ++ this.connection.acceptConnections(); ++ } ++ ++ public void initWorld(ServerLevel worldserver, ServerLevelData iworlddataserver, WorldData saveData, WorldOptions worldoptions) { ++ boolean flag = saveData.isDebugWorld(); ++ // CraftBukkit start ++ if (worldserver.generator != null) { ++ worldserver.getWorld().getPopulators().addAll(worldserver.generator.getDefaultPopulators(worldserver.getWorld())); ++ } + WorldBorder worldborder = worldserver.getWorldBorder(); ++ worldborder.applySettings(iworlddataserver.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent ++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(worldserver.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated + + if (!iworlddataserver.isInitialized()) { + try { +@@ -427,37 +732,31 @@ + iworlddataserver.setInitialized(true); + } + +- this.getPlayerList().addWorldborderListener(worldserver); +- if (this.worldData.getCustomBossEvents() != null) { +- this.getCustomBossEvents().load(this.worldData.getCustomBossEvents(), this.registryAccess()); +- } +- +- RandomSequences randomsequences = worldserver.getRandomSequences(); +- Iterator iterator = iregistry.entrySet().iterator(); +- +- while (iterator.hasNext()) { +- Entry, LevelStem> entry = (Entry) iterator.next(); +- ResourceKey resourcekey = (ResourceKey) entry.getKey(); +- +- if (resourcekey != LevelStem.OVERWORLD) { +- ResourceKey resourcekey1 = ResourceKey.create(Registries.DIMENSION, resourcekey.location()); +- DerivedLevelData secondaryworlddata = new DerivedLevelData(this.worldData, iworlddataserver); +- ServerLevel worldserver1 = new ServerLevel(this, this.executor, this.storageSource, secondaryworlddata, resourcekey1, (LevelStem) entry.getValue(), worldGenerationProgressListener, flag, j, ImmutableList.of(), false, randomsequences); +- +- worldborder.addListener(new BorderChangeListener.DelegateBorderChangeListener(worldserver1.getWorldBorder())); +- this.levels.put(resourcekey1, worldserver1); +- } +- } +- +- worldborder.applySettings(iworlddataserver.getWorldBorder()); + } ++ // CraftBukkit end + + private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) { + if (debugWorld) { + worldProperties.setSpawn(BlockPos.ZERO.above(80), 0.0F); + } else { + ServerChunkCache chunkproviderserver = world.getChunkSource(); +- ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); ++ // ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); // Paper - Move down, only attempt to find spawn position if there isn't a fixed spawn position set ++ // CraftBukkit start ++ if (world.generator != null) { ++ Random rand = new Random(world.getSeed()); ++ org.bukkit.Location spawn = world.generator.getFixedSpawnLocation(world.getWorld(), rand); ++ ++ if (spawn != null) { ++ if (spawn.getWorld() != world.getWorld()) { ++ throw new IllegalStateException("Cannot set spawn point for " + worldProperties.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")"); ++ } else { ++ worldProperties.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw()); ++ return; ++ } ++ } ++ } ++ // CraftBukkit end ++ ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set + int i = chunkproviderserver.getGenerator().getSpawnHeight(world); + + if (i < world.getMinY()) { +@@ -516,31 +815,36 @@ + iworlddataserver.setGameType(GameType.SPECTATOR); + } + +- public void prepareLevels(ChunkProgressListener worldGenerationProgressListener) { +- ServerLevel worldserver = this.overworld(); ++ // CraftBukkit start ++ public void prepareLevels(ChunkProgressListener worldloadlistener, ServerLevel worldserver) { ++ // WorldServer worldserver = this.overworld(); ++ this.forceTicks = true; ++ // CraftBukkit end + + MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location()); + BlockPos blockposition = worldserver.getSharedSpawnPos(); + +- worldGenerationProgressListener.updateSpawnPos(new ChunkPos(blockposition)); ++ worldloadlistener.updateSpawnPos(new ChunkPos(blockposition)); + ServerChunkCache chunkproviderserver = worldserver.getChunkSource(); + + this.nextTickTimeNanos = Util.getNanos(); + worldserver.setDefaultSpawnPos(blockposition, worldserver.getSharedSpawnAngle()); +- int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); ++ int i = worldserver.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world + int j = i > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(i)) : 0; + + while (chunkproviderserver.getTickingGenerated() < j) { +- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; +- this.waitUntilNextTick(); ++ // CraftBukkit start ++ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; ++ this.executeModerately(); + } + +- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; +- this.waitUntilNextTick(); +- Iterator iterator = this.levels.values().iterator(); ++ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; ++ this.executeModerately(); ++ // Iterator iterator = this.levels.values().iterator(); + +- while (iterator.hasNext()) { +- ServerLevel worldserver1 = (ServerLevel) iterator.next(); ++ if (true) { ++ ServerLevel worldserver1 = worldserver; ++ // CraftBukkit end + ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks"); + + if (forcedchunk != null) { +@@ -555,10 +859,17 @@ + } + } + +- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; +- this.waitUntilNextTick(); +- worldGenerationProgressListener.stop(); +- this.updateMobSpawningFlags(); ++ // CraftBukkit start ++ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS; ++ this.executeModerately(); ++ // CraftBukkit end ++ worldloadlistener.stop(); ++ // CraftBukkit start ++ // this.updateMobSpawningFlags(); ++ worldserver.setSpawnSettings(worldserver.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean)) ++ ++ this.forceTicks = false; ++ // CraftBukkit end + } + + public GameType getDefaultGameType() { +@@ -588,12 +899,16 @@ + worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force); + } + +- ServerLevel worldserver1 = this.overworld(); +- ServerLevelData iworlddataserver = this.worldData.overworldData(); ++ // CraftBukkit start - moved to WorldServer.save ++ /* ++ WorldServer worldserver1 = this.overworld(); ++ IWorldDataServer iworlddataserver = this.worldData.overworldData(); + + iworlddataserver.setWorldBorder(worldserver1.getWorldBorder().createSettings()); + this.worldData.setCustomBossEvents(this.getCustomBossEvents().save(this.registryAccess())); + this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData()); ++ */ ++ // CraftBukkit end + if (flush) { + Iterator iterator1 = this.getAllLevels().iterator(); + +@@ -628,18 +943,46 @@ + this.stopServer(); + } + ++ // CraftBukkit start ++ private boolean hasStopped = false; ++ private boolean hasLoggedStop = false; // Paper - Debugging ++ private final Object stopLock = new Object(); ++ public final boolean hasStopped() { ++ synchronized (this.stopLock) { ++ return this.hasStopped; ++ } ++ } ++ // CraftBukkit end ++ + public void stopServer() { ++ // CraftBukkit start - prevent double stopping on multiple threads ++ synchronized(this.stopLock) { ++ if (this.hasStopped) return; ++ this.hasStopped = true; ++ } ++ if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging ++ // CraftBukkit end + if (this.metricsRecorder.isRecording()) { + this.cancelRecordingMetrics(); + } + + MinecraftServer.LOGGER.info("Stopping server"); ++ Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing ++ // CraftBukkit start ++ if (this.server != null) { ++ this.server.spark.disable(); // Paper - spark ++ this.server.disablePlugins(); ++ this.server.waitForAsyncTasksShutdown(); // Paper - Wait for Async Tasks during shutdown ++ } ++ // CraftBukkit end ++ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.shutdown(); // Paper - Plugin remapping + this.getConnection().stop(); + this.isSaving = true; + if (this.playerList != null) { + MinecraftServer.LOGGER.info("Saving players"); + this.playerList.saveAll(); +- this.playerList.removeAll(); ++ this.playerList.removeAll(this.isRestarting); // Paper ++ try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets + } + + MinecraftServer.LOGGER.info("Saving worlds"); +@@ -693,6 +1036,15 @@ + } catch (IOException ioexception1) { + MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1); + } ++ // Spigot start ++ io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.shutdown(); // Paper ++ try { io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper ++ } catch (java.lang.InterruptedException ignored) {} // Paper ++ if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { ++ MinecraftServer.LOGGER.info("Saving usercache.json"); ++ this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving ++ } ++ // Spigot end + + } + +@@ -709,6 +1061,14 @@ + } + + public void halt(boolean waitForShutdown) { ++ // Paper start - allow passing of the intent to restart ++ this.safeShutdown(waitForShutdown, false); ++ } ++ public void safeShutdown(boolean waitForShutdown, boolean isRestarting) { ++ this.isRestarting = isRestarting; ++ this.hasLoggedStop = true; // Paper - Debugging ++ if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging ++ // Paper end + this.running = false; + if (waitForShutdown) { + try { +@@ -720,6 +1080,64 @@ + + } + ++ // Spigot Start ++ private static double calcTps(double avg, double exp, double tps) ++ { ++ return ( avg * exp ) + ( tps * ( 1 - exp ) ); ++ } ++ ++ // Paper start - Further improve server tick loop ++ private static final long SEC_IN_NANO = 1000000000; ++ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L; ++ private long lastTick = 0; ++ private long catchupTime = 0; ++ public final RollingAverage tps1 = new RollingAverage(60); ++ public final RollingAverage tps5 = new RollingAverage(60 * 5); ++ public final RollingAverage tps15 = new RollingAverage(60 * 15); ++ ++ public static class RollingAverage { ++ private final int size; ++ private long time; ++ private java.math.BigDecimal total; ++ private int index = 0; ++ private final java.math.BigDecimal[] samples; ++ private final long[] times; ++ ++ RollingAverage(int size) { ++ this.size = size; ++ this.time = size * SEC_IN_NANO; ++ this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size)); ++ this.samples = new java.math.BigDecimal[size]; ++ this.times = new long[size]; ++ for (int i = 0; i < size; i++) { ++ this.samples[i] = dec(TPS); ++ this.times[i] = SEC_IN_NANO; ++ } ++ } ++ ++ private static java.math.BigDecimal dec(long t) { ++ return new java.math.BigDecimal(t); ++ } ++ public void add(java.math.BigDecimal x, long t) { ++ time -= times[index]; ++ total = total.subtract(samples[index].multiply(dec(times[index]))); ++ samples[index] = x; ++ times[index] = t; ++ time += t; ++ total = total.add(x.multiply(dec(t))); ++ if (++index == size) { ++ index = 0; ++ } ++ } ++ ++ public double getAverage() { ++ return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue(); ++ } ++ } ++ private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL)); ++ // Paper end ++ // Spigot End ++ + protected void runServer() { + try { + if (!this.initServer()) { +@@ -727,9 +1145,27 @@ + } + + this.nextTickTimeNanos = Util.getNanos(); +- this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse((Object) null); ++ this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse(null); // CraftBukkit - decompile error + this.status = this.buildServerStatus(); + ++ this.server.spark.enableBeforePlugins(); // Paper - spark ++ // Spigot start ++ org.spigotmc.WatchdogThread.hasStarted = true; // Paper ++ Arrays.fill( this.recentTps, 20 ); ++ // Paper start - further improve server tick loop ++ long tickSection = Util.getNanos(); ++ long currentTime; ++ // Paper end - further improve server tick loop ++ // Paper start - Add onboarding message for initial server start ++ if (io.papermc.paper.configuration.GlobalConfiguration.isFirstStart) { ++ LOGGER.info("*************************************************************************************"); ++ LOGGER.info("This is the first time you're starting this server."); ++ LOGGER.info("It's recommended you read our 'Getting Started' documentation for guidance."); ++ LOGGER.info("View this and more helpful information here: https://docs.papermc.io/paper/next-steps"); ++ LOGGER.info("*************************************************************************************"); ++ } ++ // Paper end - Add onboarding message for initial server start ++ + while (this.running) { + long i; + +@@ -744,11 +1180,30 @@ + if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) { + long k = j / i; + ++ if (this.server.getWarnOnOverload()) // CraftBukkit + MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", j / TimeUtil.NANOSECONDS_PER_MILLISECOND, k); + this.nextTickTimeNanos += k * i; + this.lastOverloadWarningNanos = this.nextTickTimeNanos; + } ++ } ++ // Spigot start ++ // Paper start - further improve server tick loop ++ currentTime = Util.getNanos(); ++ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) { ++ final long diff = currentTime - tickSection; ++ final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP); ++ tps1.add(currentTps, diff); ++ tps5.add(currentTps, diff); ++ tps15.add(currentTps, diff); ++ ++ // Backwards compat with bad plugins ++ this.recentTps[0] = tps1.getAverage(); ++ this.recentTps[1] = tps5.getAverage(); ++ this.recentTps[2] = tps15.getAverage(); ++ tickSection = currentTime; + } ++ // Paper end - further improve server tick loop ++ // Spigot end + + boolean flag = i == 0L; + +@@ -757,6 +1212,8 @@ + this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount); + } + ++ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time ++ lastTick = currentTime; + this.nextTickTimeNanos += i; + + try { +@@ -830,6 +1287,14 @@ + this.services.profileCache().clearExecutor(); + } + ++ org.spigotmc.WatchdogThread.doStop(); // Spigot ++ // CraftBukkit start - Restore terminal to original settings ++ try { ++ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender ++ } catch (Exception ignored) { ++ } ++ // CraftBukkit end ++ io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown + this.onServerExit(); + } + +@@ -889,9 +1354,16 @@ + } + + private boolean haveTime() { +- return this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos); ++ // CraftBukkit start ++ return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos); + } + ++ private void executeModerately() { ++ this.runAllTasks(); ++ java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L); ++ // CraftBukkit end ++ } ++ + public static boolean throwIfFatalException() { + RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get(); + +@@ -903,7 +1375,7 @@ + } + + public static void setFatalException(RuntimeException exception) { +- MinecraftServer.fatalException.compareAndSet((Object) null, exception); ++ MinecraftServer.fatalException.compareAndSet(null, exception); // CraftBukkit - decompile error + } + + @Override +@@ -961,6 +1433,7 @@ + if (super.pollTask()) { + return true; + } else { ++ boolean ret = false; // Paper - force execution of all worlds, do not just bias the first + if (this.tickRateManager.isSprinting() || this.haveTime()) { + Iterator iterator = this.getAllLevels().iterator(); + +@@ -968,16 +1441,16 @@ + ServerLevel worldserver = (ServerLevel) iterator.next(); + + if (worldserver.getChunkSource().pollTask()) { +- return true; ++ ret = true; // Paper - force execution of all worlds, do not just bias the first + } + } + } + +- return false; ++ return ret; // Paper - force execution of all worlds, do not just bias the first + } + } + +- public void doRunTask(TickTask ticktask) { ++ public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error + Profiler.get().incrementCounter("runTask"); + super.doRunTask(ticktask); + } +@@ -1025,27 +1498,45 @@ + } + + public void tickServer(BooleanSupplier shouldKeepTicking) { ++ org.spigotmc.WatchdogThread.tick(); // Spigot + long i = Util.getNanos(); + int j = this.pauseWhileEmptySeconds() * 20; + ++ this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping + if (j > 0) { +- if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting()) { ++ if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping + ++this.emptyTicks; + } else { + this.emptyTicks = 0; + } + + if (this.emptyTicks >= j) { ++ this.server.spark.tickStart(); // Paper - spark + if (this.emptyTicks == j) { + MinecraftServer.LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhileEmptySeconds()); + this.autoSave(); + } + ++ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit ++ // Paper start - avoid issues with certain tasks not processing during sleep ++ Runnable task; ++ while ((task = this.processQueue.poll()) != null) { ++ task.run(); ++ } ++ for (final ServerLevel level : this.levels.values()) { ++ // process unloads ++ level.getChunkSource().tick(() -> true, false); ++ } ++ // Paper end - avoid issues with certain tasks not processing during sleep ++ this.server.spark.executeMainThreadTasks(); // Paper - spark + this.tickConnection(); ++ this.server.spark.tickEnd(((double)(System.nanoTime() - lastTick) / 1000000D)); // Paper - spark + return; + } + } + ++ this.server.spark.tickStart(); // Paper - spark ++ new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper - Server Tick Events + ++this.tickCount; + this.tickRateManager.tick(); + this.tickChildren(shouldKeepTicking); +@@ -1055,12 +1546,20 @@ + } + + --this.ticksUntilAutosave; +- if (this.ticksUntilAutosave <= 0) { ++ if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit + this.autoSave(); + } + + ProfilerFiller gameprofilerfiller = Profiler.get(); + ++ this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings) ++ this.server.spark.executeMainThreadTasks(); // Paper - spark ++ // Paper start - Server Tick Events ++ long endTime = System.nanoTime(); ++ long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime; ++ new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent(); ++ // Paper end - Server Tick Events ++ this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark + gameprofilerfiller.push("tallying"); + long k = Util.getNanos() - i; + int l = this.tickCount % 100; +@@ -1069,12 +1568,17 @@ + this.aggregatedTickTimesNanos += k; + this.tickTimesNanos[l] = k; + this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F; ++ // Paper start - Add tick times API and /mspt command ++ this.tickTimes5s.add(this.tickCount, k); ++ this.tickTimes10s.add(this.tickCount, k); ++ this.tickTimes60s.add(this.tickCount, k); ++ // Paper end - Add tick times API and /mspt command + this.logTickMethodTime(i); + gameprofilerfiller.pop(); + } + + private void autoSave() { +- this.ticksUntilAutosave = this.computeNextAutosaveInterval(); ++ this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit + MinecraftServer.LOGGER.debug("Autosave started"); + ProfilerFiller gameprofilerfiller = Profiler.get(); + +@@ -1123,7 +1627,7 @@ + private ServerStatus buildServerStatus() { + ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus(); + +- return new ServerStatus(Component.nullToEmpty(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile()); ++ return new ServerStatus(io.papermc.paper.adventure.PaperAdventure.asVanilla(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile()); // Paper - Adventure + } + + private ServerStatus.Players buildPlayerStatus() { +@@ -1133,7 +1637,7 @@ + if (this.hidesOnlinePlayers()) { + return new ServerStatus.Players(i, list.size(), List.of()); + } else { +- int j = Math.min(list.size(), 12); ++ int j = Math.min(list.size(), org.spigotmc.SpigotConfig.playerSample); // Paper - PaperServerListPingEvent + ObjectArrayList objectarraylist = new ObjectArrayList(j); + int k = Mth.nextInt(this.random, 0, list.size() - j); + +@@ -1154,24 +1658,72 @@ + this.getPlayerList().getPlayers().forEach((entityplayer) -> { + entityplayer.connection.suspendFlushing(); + }); ++ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit ++ // Paper start - Folia scheduler API ++ ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).tick(); ++ getAllLevels().forEach(level -> { ++ for (final Entity entity : level.getEntities().getAll()) { ++ if (entity.isRemoved()) { ++ continue; ++ } ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ }); ++ // Paper end - Folia scheduler API ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + gameprofilerfiller.push("commandFunctions"); + this.getFunctions().tick(); + gameprofilerfiller.popPush("levels"); +- Iterator iterator = this.getAllLevels().iterator(); ++ //Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; moved down + ++ // CraftBukkit start ++ // Run tasks that are waiting on processing ++ while (!this.processQueue.isEmpty()) { ++ this.processQueue.remove().run(); ++ } ++ ++ // Send time updates to everyone, it will get the right time from the world the player is in. ++ // Paper start - Perf: Optimize time updates ++ for (final ServerLevel level : this.getAllLevels()) { ++ final boolean doDaylight = level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT); ++ final long dayTime = level.getDayTime(); ++ long worldTime = level.getGameTime(); ++ final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight); ++ for (Player entityhuman : level.players()) { ++ if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) { ++ continue; ++ } ++ ServerPlayer entityplayer = (ServerPlayer) entityhuman; ++ long playerTime = entityplayer.getPlayerTime(); ++ ClientboundSetTimePacket packet = (playerTime == dayTime) ? worldPacket : ++ new ClientboundSetTimePacket(worldTime, playerTime, doDaylight); ++ entityplayer.connection.send(packet); // Add support for per player time ++ // Paper end - Perf: Optimize time updates ++ } ++ } ++ ++ this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked ++ Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); ++ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent ++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + + gameprofilerfiller.push(() -> { + String s = String.valueOf(worldserver); + + return s + " " + String.valueOf(worldserver.dimension().location()); + }); ++ /* Drop global time updates + if (this.tickCount % 20 == 0) { + gameprofilerfiller.push("timeSync"); + this.synchronizeTime(worldserver); + gameprofilerfiller.pop(); + } ++ // CraftBukkit end */ + + gameprofilerfiller.push("tick"); + +@@ -1186,7 +1738,9 @@ + + gameprofilerfiller.pop(); + gameprofilerfiller.pop(); ++ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions + } ++ this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked + + gameprofilerfiller.popPush("connection"); + this.tickConnection(); +@@ -1267,6 +1821,22 @@ + return (ServerLevel) this.levels.get(key); + } + ++ // CraftBukkit start ++ public void addLevel(ServerLevel level) { ++ Map, ServerLevel> oldLevels = this.levels; ++ Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); ++ newLevels.put(level.dimension(), level); ++ this.levels = Collections.unmodifiableMap(newLevels); ++ } ++ ++ public void removeLevel(ServerLevel level) { ++ Map, ServerLevel> oldLevels = this.levels; ++ Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); ++ newLevels.remove(level.dimension()); ++ this.levels = Collections.unmodifiableMap(newLevels); ++ } ++ // CraftBukkit end ++ + public Set> levelKeys() { + return this.levels.keySet(); + } +@@ -1296,7 +1866,7 @@ + + @DontObfuscate + public String getServerModName() { +- return "vanilla"; ++ return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper + } + + public SystemReport fillSystemReport(SystemReport details) { +@@ -1347,7 +1917,7 @@ + + @Override + public void sendSystemMessage(Component message) { +- MinecraftServer.LOGGER.info(message.getString()); ++ MinecraftServer.LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(message))); // Paper - Log message with colors + } + + public KeyPair getKeyPair() { +@@ -1385,11 +1955,14 @@ + } + } + +- public void setDifficulty(Difficulty difficulty, boolean forceUpdate) { +- if (forceUpdate || !this.worldData.isDifficultyLocked()) { +- this.worldData.setDifficulty(this.worldData.isHardcore() ? Difficulty.HARD : difficulty); +- this.updateMobSpawningFlags(); +- this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate); ++ // Paper start - per level difficulty ++ public void setDifficulty(ServerLevel level, Difficulty difficulty, boolean forceUpdate) { ++ PrimaryLevelData worldData = level.serverLevelData; ++ if (forceUpdate || !worldData.isDifficultyLocked()) { ++ worldData.setDifficulty(worldData.isHardcore() ? Difficulty.HARD : difficulty); ++ level.setSpawnSettings(worldData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters); ++ // this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate); ++ // Paper end - per level difficulty + } + } + +@@ -1403,7 +1976,7 @@ + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + +- worldserver.setSpawnSettings(this.isSpawningMonsters()); ++ worldserver.setSpawnSettings(worldserver.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean)) + } + + } +@@ -1481,10 +2054,20 @@ + + @Override + public String getMotd() { +- return this.motd; ++ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.motd); // Paper - Adventure + } + + public void setMotd(String motd) { ++ // Paper start - Adventure ++ this.motd = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOr(motd, net.kyori.adventure.text.Component.empty()); ++ } ++ ++ public net.kyori.adventure.text.Component motd() { ++ return this.motd; ++ } ++ ++ public void motd(net.kyori.adventure.text.Component motd) { ++ // Paper end - Adventure + this.motd = motd; + } + +@@ -1507,7 +2090,7 @@ + } + + public ServerConnectionListener getConnection() { +- return this.connection; ++ return this.connection == null ? this.connection = new ServerConnectionListener(this) : this.connection; // Spigot + } + + public boolean isReady() { +@@ -1593,7 +2176,7 @@ + @Override + public void executeIfPossible(Runnable runnable) { + if (this.isStopped()) { +- throw new RejectedExecutionException("Server already shutting down"); ++ throw new io.papermc.paper.util.ServerStopRejectedExecutionException("Server already shutting down"); // Paper - do not prematurely disconnect players on stop + } else { + super.executeIfPossible(runnable); + } +@@ -1632,16 +2215,22 @@ + return this.functionManager; + } + ++ // Paper start - Add ServerResourcesReloadedEvent ++ @Deprecated @io.papermc.paper.annotation.DoNotUse + public CompletableFuture reloadResources(Collection dataPacks) { ++ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end - Add ServerResourcesReloadedEvent + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { +- Stream stream = dataPacks.stream(); ++ Stream stream = dataPacks.stream(); // CraftBukkit - decompile error + PackRepository resourcepackrepository = this.packRepository; + + Objects.requireNonNull(this.packRepository); +- return (ImmutableList) stream.map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); ++ return stream.map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error // todo: is this needed anymore? + }, this).thenCompose((immutablelist) -> { + MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); +- List> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); ++ List> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess(), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag lifecycle - add cause + + return ReloadableServerResources.loadResources(resourcemanager, this.registries, list, this.worldData.enabledFeatures(), this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, this.getFunctionCompilationLevel(), this.executor, this).whenComplete((datapackresources, throwable) -> { + if (throwable != null) { +@@ -1652,6 +2241,7 @@ + return new MinecraftServer.ReloadableResources(resourcemanager, datapackresources); + }); + }).thenAcceptAsync((minecraftserver_reloadableresources) -> { ++ io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), minecraftserver_reloadableresources.managers().commands); // Paper + this.resources.close(); + this.resources = minecraftserver_reloadableresources; + this.packRepository.setSelected(dataPacks); +@@ -1660,11 +2250,23 @@ + this.worldData.setDataConfiguration(worlddataconfiguration); + this.resources.managers.updateStaticRegistryTags(); + this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures()); ++ this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes + this.getPlayerList().saveAll(); + this.getPlayerList().reloadResources(); + this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary()); + this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager); + this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures()); ++ org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here ++ // Paper start - brigadier command API ++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins ++ final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap(); ++ helpMap.clear(); ++ helpMap.initializeGeneralTopics(); ++ helpMap.initializeCommands(); ++ this.server.syncCommands(); // Refresh commands after event ++ // Paper end ++ new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded + }, this); + + if (this.isSameThread()) { +@@ -1789,14 +2391,15 @@ + if (this.isEnforceWhitelist()) { + PlayerList playerlist = source.getServer().getPlayerList(); + UserWhiteList whitelist = playerlist.getWhiteList(); ++ if (!((DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled + List list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { +- entityplayer.connection.disconnect((Component) Component.translatable("multiplayer.disconnect.not_whitelisted")); ++ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) ++ entityplayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause + } + } + +@@ -1952,7 +2555,7 @@ + final List list = Lists.newArrayList(); + final GameRules gamerules = this.getGameRules(); + +- gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor(this) { ++ gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor() { // CraftBukkit - decompile error + @Override + public > void visit(GameRules.Key key, GameRules.Type type) { + list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key))); +@@ -2058,7 +2661,7 @@ + try { + label51: + { +- ArrayList arraylist; ++ ArrayList arraylist; // CraftBukkit - decompile error + + try { + arraylist = Lists.newArrayList(NativeModuleLister.listModules()); +@@ -2105,8 +2708,23 @@ + if (bufferedwriter != null) { + bufferedwriter.close(); + } ++ ++ } ++ ++ // CraftBukkit start ++ public boolean isDebugging() { ++ return false; ++ } ++ ++ public static MinecraftServer getServer() { ++ return SERVER; // Paper ++ } + ++ @Deprecated ++ public static RegistryAccess getDefaultRegistryAccess() { ++ return CraftRegistry.getMinecraftRegistry(); + } ++ // CraftBukkit end + + private ProfilerFiller createProfiler() { + if (this.willStartRecordingMetrics) { +@@ -2225,18 +2843,24 @@ + } + + public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) { +- String s1 = params.decorate(message).getString(); ++ // Paper start ++ net.kyori.adventure.text.Component s1 = io.papermc.paper.adventure.PaperAdventure.asAdventure(params.decorate(message)); + + if (prefix != null) { +- MinecraftServer.LOGGER.info("[{}] {}", prefix, s1); ++ MinecraftServer.COMPONENT_LOGGER.info("[{}] {}", prefix, s1); + } else { +- MinecraftServer.LOGGER.info("{}", s1); ++ MinecraftServer.COMPONENT_LOGGER.info("{}", s1); ++ // Paper end + } + + } + ++ public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool( ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper ++ ++ public final ChatDecorator improvedChatDecorator = new io.papermc.paper.adventure.ImprovedChatDecorator(this); // Paper - adventure + public ChatDecorator getChatDecorator() { +- return ChatDecorator.PLAIN; ++ return this.improvedChatDecorator; // Paper - support async chat decoration events + } + + public boolean logIPs() { +@@ -2379,4 +3003,53 @@ + public static record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) { + + } ++ ++ // Paper start - Add tick times API and /mspt command ++ public static class TickTimes { ++ private final long[] times; ++ ++ public TickTimes(int length) { ++ times = new long[length]; ++ } ++ ++ void add(int index, long time) { ++ times[index % times.length] = time; ++ } ++ ++ public long[] getTimes() { ++ return times.clone(); ++ } ++ ++ public double getAverage() { ++ long total = 0L; ++ for (long value : times) { ++ total += value; ++ } ++ return ((double) total / (double) times.length) * 1.0E-6D; ++ } ++ } ++ // Paper end - Add tick times API and /mspt command ++ ++ // Paper start - API to check if the server is sleeping ++ public boolean isTickPaused() { ++ return this.emptyTicks > 0 && this.emptyTicks >= this.pauseWhileEmptySeconds() * 20; ++ } ++ ++ public void addPluginAllowingSleep(final String pluginName, final boolean value) { ++ if (!value) { ++ this.pluginsBlockingSleep.add(pluginName); ++ } else { ++ this.pluginsBlockingSleep.remove(pluginName); ++ } ++ } ++ ++ private void removeDisabledPluginsBlockingSleep() { ++ if (this.pluginsBlockingSleep.isEmpty()) { ++ return; ++ } ++ this.pluginsBlockingSleep.removeIf(plugin -> ( ++ !io.papermc.paper.plugin.manager.PaperPluginManagerImpl.getInstance().isPluginEnabled(plugin) ++ )); ++ } ++ // Paper end - API to check if the server is sleeping + } diff --git a/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch new file mode 100644 index 0000000000..93d25149bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -50,7 +50,7 @@ + public class PlayerAdvancements { + + private static final Logger LOGGER = LogUtils.getLogger(); +- private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); ++ private static final Gson GSON = (new GsonBuilder()).create(); // Paper - Remove pretty printing from advancements + private final PlayerList playerList; + private final Path playerSavePath; + private AdvancementTree tree; +@@ -63,6 +63,7 @@ + private AdvancementHolder lastSelectedTab; + private boolean isFirstPacket = true; + private final Codec codec; ++ public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage + + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) { + this.playerList = playerManager; +@@ -162,6 +163,7 @@ + } + + public void save() { ++ if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return; // Spigot + JsonElement jsonelement = (JsonElement) this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow(); + + try { +@@ -196,6 +198,7 @@ + AdvancementHolder advancementholder = loader.get(minecraftkey); + + if (advancementholder == null) { ++ if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit + PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath); + } else { + this.startProgress(advancementholder, advancementprogress); +@@ -223,14 +226,31 @@ + boolean flag1 = advancementprogress.isDone(); + + if (advancementprogress.grantProgress(criterionName)) { ++ // Paper start - Add PlayerAdvancementCriterionGrantEvent ++ if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.toBukkit(), criterionName).callEvent()) { ++ advancementprogress.revokeProgress(criterionName); ++ return false; ++ } ++ // Paper end - Add PlayerAdvancementCriterionGrantEvent + this.unregisterListeners(advancement); + this.progressChanged.add(advancement); + flag = true; + if (!flag1 && advancementprogress.isDone()) { ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { ++ return java.util.Optional.ofNullable( ++ info.shouldAnnounceChat() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(info.getType().createAnnouncement(advancement, this.player)) : null ++ ); ++ }).orElse(null); ++ final org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit(), message); ++ this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ // Paper end + advancement.value().rewards().grant(this.player); + advancement.value().display().ifPresent((advancementdisplay) -> { +- if (advancementdisplay.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { +- this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false); ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { ++ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); ++ // Paper end + } + + }); diff --git a/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch b/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch new file mode 100644 index 0000000000..6c906a073c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/server/ReloadableServerRegistries.java ++++ b/net/minecraft/server/ReloadableServerRegistries.java +@@ -50,8 +50,9 @@ + ); + HolderLookup.Provider provider = HolderLookup.Provider.create(list.stream()); + RegistryOps registryOps = provider.createSerializationContext(JsonOps.INSTANCE); ++ final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper + List>> list2 = LootDataType.values() +- .map(type -> scheduleRegistryLoad((LootDataType)type, registryOps, resourceManager, prepareExecutor)) ++ .map(type -> scheduleRegistryLoad((LootDataType)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper + .toList(); + CompletableFuture>> completableFuture = Util.sequence(list2); + return completableFuture.thenApplyAsync( +@@ -60,14 +61,15 @@ + } + + private static CompletableFuture> scheduleRegistryLoad( +- LootDataType type, RegistryOps ops, ResourceManager resourceManager, Executor prepareExecutor ++ LootDataType type, RegistryOps ops, ResourceManager resourceManager, Executor prepareExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper + ) { + return CompletableFuture.supplyAsync(() -> { + WritableRegistry writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental()); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry + Map map = new HashMap<>(); + SimpleJsonResourceReloadListener.scanDirectory(resourceManager, type.registryKey(), ops, type.codec(), map); +- map.forEach((id, value) -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO)); +- TagLoader.loadTagsForRegistry(resourceManager, writableRegistry); ++ map.forEach((id, value) -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO, conversions)); // Paper - register with listeners ++ TagLoader.loadTagsForRegistry(resourceManager, writableRegistry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag life cycle - reload + return writableRegistry; + }, prepareExecutor); + } diff --git a/paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch b/paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch new file mode 100644 index 0000000000..59120184c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/server/ReloadableServerResources.java ++++ b/net/minecraft/server/ReloadableServerResources.java +@@ -39,6 +39,7 @@ + this.postponedTags = pendingTagLoads; + this.recipes = new RecipeManager(registries); + this.commands = new Commands(environment, CommandBuildContext.simple(registries, enabledFeatures)); ++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(registries, enabledFeatures)); // Paper - Brigadier Command API + this.advancements = new ServerAdvancementManager(registries); + this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher()); + } +@@ -83,6 +84,14 @@ + ReloadableServerResources reloadableServerResources = new ReloadableServerResources( + reloadResult.layers(), reloadResult.lookupWithUpdatedTags(), enabledFeatures, environment, pendingTagLoads, functionPermissionLevel + ); ++ // Paper start - call commands event for bootstraps ++ //noinspection ConstantValue ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent( ++ io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, ++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, ++ io.papermc.paper.plugin.bootstrap.BootstrapContext.class, ++ MinecraftServer.getServer() == null ? io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL : io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); ++ // Paper end - call commands event + return SimpleReloadInstance.create( + resourceManager, + reloadableServerResources.listeners(), diff --git a/paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch new file mode 100644 index 0000000000..5a5a995288 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/server/ServerAdvancementManager.java ++++ b/net/minecraft/server/ServerAdvancementManager.java +@@ -21,10 +21,14 @@ + import net.minecraft.util.profiling.ProfilerFiller; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.HashMap; ++// CraftBukkit end ++ + public class ServerAdvancementManager extends SimpleJsonResourceReloadListener { + + private static final Logger LOGGER = LogUtils.getLogger(); +- public Map advancements = Map.of(); ++ public Map advancements = new HashMap<>(); // CraftBukkit - SPIGOT-7734: mutable + private AdvancementTree tree = new AdvancementTree(); + private final HolderLookup.Provider registries; + +@@ -37,13 +41,19 @@ + Builder builder = ImmutableMap.builder(); + + prepared.forEach((minecraftkey, advancement) -> { ++ // Spigot start ++ if (org.spigotmc.SpigotConfig.disabledAdvancements != null && (org.spigotmc.SpigotConfig.disabledAdvancements.contains("*") || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.toString()) || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.getNamespace()))) { ++ return; ++ } ++ // Spigot end + this.validate(minecraftkey, advancement); + builder.put(minecraftkey, new AdvancementHolder(minecraftkey, advancement)); + }); +- this.advancements = builder.buildOrThrow(); ++ this.advancements = new HashMap<>(builder.buildOrThrow()); // CraftBukkit - SPIGOT-7734: mutable + AdvancementTree advancementtree = new AdvancementTree(); + + advancementtree.addAll(this.advancements.values()); ++ LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll + Iterator iterator = advancementtree.roots().iterator(); + + while (iterator.hasNext()) { diff --git a/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch new file mode 100644 index 0000000000..a5f6fd5796 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/ServerFunctionLibrary.java ++++ b/net/minecraft/server/ServerFunctionLibrary.java +@@ -113,7 +113,7 @@ + return null; + }).join()); + this.functions = builder.build(); +- this.tags = this.tagsLoader.build((Map>)intermediate.getFirst()); ++ this.tags = this.tagsLoader.build((Map>)intermediate.getFirst(), null); // Paper - command function tags are not implemented yet + }, + applyExecutor + ); diff --git a/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch new file mode 100644 index 0000000000..45ee083997 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/ServerFunctionManager.java ++++ b/net/minecraft/server/ServerFunctionManager.java +@@ -37,7 +37,7 @@ + } + + public CommandDispatcher getDispatcher() { +- return this.server.getCommands().getDispatcher(); ++ return this.server.getCommands().getDispatcher(); // CraftBukkit // Paper - Don't override command dispatcher + } + + public void tick() { diff --git a/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch new file mode 100644 index 0000000000..712e8444b1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch @@ -0,0 +1,168 @@ +--- a/net/minecraft/server/ServerScoreboard.java ++++ b/net/minecraft/server/ServerScoreboard.java +@@ -42,7 +42,7 @@ + protected void onScoreChanged(ScoreHolder scoreHolder, Objective objective, Score score) { + super.onScoreChanged(scoreHolder, objective, score); + if (this.trackedObjectives.contains(objective)) { +- this.server.getPlayerList().broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat()))); ++ this.broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat()))); // CraftBukkit + } + + this.setDirty(); +@@ -57,7 +57,7 @@ + @Override + public void onPlayerRemoved(ScoreHolder scoreHolder) { + super.onPlayerRemoved(scoreHolder); +- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null)); ++ this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null)); // CraftBukkit + this.setDirty(); + } + +@@ -65,7 +65,7 @@ + public void onPlayerScoreRemoved(ScoreHolder scoreHolder, Objective objective) { + super.onPlayerScoreRemoved(scoreHolder, objective); + if (this.trackedObjectives.contains(objective)) { +- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), objective.getName())); ++ this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), objective.getName())); // CraftBukkit + } + + this.setDirty(); +@@ -78,7 +78,7 @@ + super.setDisplayObjective(slot, objective); + if (scoreboardobjective1 != objective && scoreboardobjective1 != null) { + if (this.getObjectiveDisplaySlotCount(scoreboardobjective1) > 0) { +- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); ++ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); // CraftBukkit + } else { + this.stopTrackingObjective(scoreboardobjective1); + } +@@ -86,7 +86,7 @@ + + if (objective != null) { + if (this.trackedObjectives.contains(objective)) { +- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); ++ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); // CraftBukkit + } else { + this.startTrackingObjective(objective); + } +@@ -98,7 +98,7 @@ + @Override + public boolean addPlayerToTeam(String scoreHolderName, PlayerTeam team) { + if (super.addPlayerToTeam(scoreHolderName, team)) { +- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD)); ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD)); // CraftBukkit + this.setDirty(); + return true; + } else { +@@ -106,13 +106,43 @@ + } + } + ++ // Paper start - Multiple Entries with Scoreboards ++ public boolean addPlayersToTeam(java.util.Collection players, PlayerTeam team) { ++ boolean anyAdded = false; ++ for (String playerName : players) { ++ if (super.addPlayerToTeam(playerName, team)) { ++ anyAdded = true; ++ } ++ } ++ ++ if (anyAdded) { ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD)); ++ this.setDirty(); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) { + super.removePlayerFromTeam(scoreHolderName, team); +- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE)); ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE)); // CraftBukkit + this.setDirty(); + } + ++ // Paper start - Multiple Entries with Scoreboards ++ public void removePlayersFromTeam(java.util.Collection players, PlayerTeam team) { ++ for (String playerName : players) { ++ super.removePlayerFromTeam(playerName, team); ++ } ++ ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE)); ++ this.setDirty(); ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public void onObjectiveAdded(Objective objective) { + super.onObjectiveAdded(objective); +@@ -123,7 +153,7 @@ + public void onObjectiveChanged(Objective objective) { + super.onObjectiveChanged(objective); + if (this.trackedObjectives.contains(objective)) { +- this.server.getPlayerList().broadcastAll(new ClientboundSetObjectivePacket(objective, 2)); ++ this.broadcastAll(new ClientboundSetObjectivePacket(objective, 2)); // CraftBukkit + } + + this.setDirty(); +@@ -142,21 +172,21 @@ + @Override + public void onTeamAdded(PlayerTeam team) { + super.onTeamAdded(team); +- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); // CraftBukkit + this.setDirty(); + } + + @Override + public void onTeamChanged(PlayerTeam team) { + super.onTeamChanged(team); +- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false)); ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false)); // CraftBukkit + this.setDirty(); + } + + @Override + public void onTeamRemoved(PlayerTeam team) { + super.onTeamRemoved(team); +- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team)); ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team)); // CraftBukkit + this.setDirty(); + } + +@@ -207,6 +237,7 @@ + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); ++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { +@@ -243,6 +274,7 @@ + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); ++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { +@@ -287,6 +319,16 @@ + return this.createData().load(nbt, registries); + } + ++ // CraftBukkit start - Send to players ++ private void broadcastAll(Packet packet) { ++ for (ServerPlayer entityplayer : (List) this.server.getPlayerList().players) { ++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) { ++ entityplayer.connection.send(packet); ++ } ++ } ++ } ++ // CraftBukkit end ++ + public static enum Method { + + CHANGE, REMOVE; diff --git a/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch new file mode 100644 index 0000000000..9720b17dc9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/server/ServerTickRateManager.java ++++ b/net/minecraft/server/ServerTickRateManager.java +@@ -59,8 +59,14 @@ + } + + public boolean stopSprinting() { ++ // CraftBukkit start, add sendLog parameter ++ return this.stopSprinting(true); ++ } ++ ++ public boolean stopSprinting(boolean sendLog) { ++ // CraftBukkit end + if (this.remainingSprintTicks > 0L) { +- this.finishTickSprint(); ++ this.finishTickSprint(sendLog); // CraftBukkit - add sendLog parameter + return true; + } else { + return false; +@@ -78,7 +84,7 @@ + return flag; + } + +- private void finishTickSprint() { ++ private void finishTickSprint(boolean sendLog) { // CraftBukkit - add sendLog parameter + long i = this.scheduledCurrentSprintTicks - this.remainingSprintTicks; + double d0 = Math.max(1.0D, (double) this.sprintTimeSpend) / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND; + int j = (int) ((double) (TimeUtil.MILLISECONDS_PER_SECOND * i) / d0); +@@ -86,9 +92,13 @@ + + this.scheduledCurrentSprintTicks = 0L; + this.sprintTimeSpend = 0L; +- this.server.createCommandSourceStack().sendSuccess(() -> { +- return Component.translatable("commands.tick.sprint.report", j, s); +- }, true); ++ // CraftBukkit start - add sendLog parameter ++ if (sendLog) { ++ this.server.createCommandSourceStack().sendSuccess(() -> { ++ return Component.translatable("commands.tick.sprint.report", j, s); ++ }, true); ++ } ++ // CraftBukkit end + this.remainingSprintTicks = 0L; + this.setFrozen(this.previousIsFrozen); + this.server.onTickRateChanged(); +@@ -102,7 +112,7 @@ + --this.remainingSprintTicks; + return true; + } else { +- this.finishTickSprint(); ++ this.finishTickSprint(true); // CraftBukkit - add sendLog parameter + return false; + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/Services.java.patch b/paper-server/patches/sources/net/minecraft/server/Services.java.patch new file mode 100644 index 0000000000..c556957aab --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/Services.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/server/Services.java ++++ b/net/minecraft/server/Services.java +@@ -10,16 +10,32 @@ + import net.minecraft.server.players.GameProfileCache; + import net.minecraft.util.SignatureValidator; + ++ + public record Services( +- MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache ++ MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache, @javax.annotation.Nullable io.papermc.paper.configuration.PaperConfigurations paperConfigurations // Paper - add paper configuration files + ) { +- private static final String USERID_CACHE_FILE = "usercache.json"; ++ // Paper start - add paper configuration files ++ public Services(MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache) { ++ this(sessionService, servicesKeySet, profileRepository, profileCache, null); ++ } + +- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory) { ++ @Override ++ public io.papermc.paper.configuration.PaperConfigurations paperConfigurations() { ++ return java.util.Objects.requireNonNull(this.paperConfigurations); ++ } ++ // Paper end - add paper configuration files ++ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public ++ ++ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter + MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); + GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); +- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json")); +- return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache); ++ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - use specified user cache file ++ // Paper start - load paper config files from cli options ++ final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); ++ final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath(); ++ io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings")); ++ return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache, paperConfigurations); ++ // Paper end - load paper config files from cli options + } + + @Nullable diff --git a/paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch b/paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch new file mode 100644 index 0000000000..c95f9d743c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/WorldLoader.java ++++ b/net/minecraft/server/WorldLoader.java +@@ -37,7 +37,7 @@ + CloseableResourceManager closeableResourceManager = pair.getSecond(); + LayeredRegistryAccess layeredRegistryAccess = RegistryLayer.createRegistryAccess(); + List> list = TagLoader.loadTagsForExistingRegistries( +- closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC) ++ closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL // Paper - tag lifecycle - add cause + ); + RegistryAccess.Frozen frozen = layeredRegistryAccess.getAccessForLoading(RegistryLayer.WORLDGEN); + List> list2 = TagLoader.buildUpdatedLookups(frozen, list); diff --git a/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch b/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch new file mode 100644 index 0000000000..6e80d59765 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/server/bossevents/CustomBossEvent.java ++++ b/net/minecraft/server/bossevents/CustomBossEvent.java +@@ -18,6 +18,10 @@ + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; + import net.minecraft.world.BossEvent; ++// CraftBukkit start ++import org.bukkit.boss.KeyedBossBar; ++import org.bukkit.craftbukkit.boss.CraftKeyedBossbar; ++// CraftBukkit end + + public class CustomBossEvent extends ServerBossEvent { + +@@ -25,7 +29,17 @@ + private final Set players = Sets.newHashSet(); + private int value; + private int max = 100; ++ // CraftBukkit start ++ private KeyedBossBar bossBar; + ++ public KeyedBossBar getBukkitEntity() { ++ if (this.bossBar == null) { ++ this.bossBar = new CraftKeyedBossbar(this); ++ } ++ return this.bossBar; ++ } ++ // CraftBukkit end ++ + public CustomBossEvent(ResourceLocation id, Component displayName) { + super(displayName, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS); + this.id = id; diff --git a/paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch new file mode 100644 index 0000000000..be620cd58a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/BanIpCommands.java ++++ b/net/minecraft/server/commands/BanIpCommands.java +@@ -66,7 +66,7 @@ + } + + for (ServerPlayer serverPlayer : list) { +- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned")); ++ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"), org.bukkit.event.player.PlayerKickEvent.Cause.IP_BANNED); // Paper - kick event cause + } + + return list.size(); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch new file mode 100644 index 0000000000..a6e8c45f71 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/BanPlayerCommands.java ++++ b/net/minecraft/server/commands/BanPlayerCommands.java +@@ -55,7 +55,7 @@ + ); + ServerPlayer serverPlayer = source.getServer().getPlayerList().getPlayer(gameProfile.getId()); + if (serverPlayer != null) { +- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned")); ++ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch new file mode 100644 index 0000000000..5009b1bdcb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/DeOpCommands.java ++++ b/net/minecraft/server/commands/DeOpCommands.java +@@ -35,7 +35,7 @@ + if (playerList.isOp(gameProfile)) { + playerList.deop(gameProfile); + i++; +- source.sendSuccess(() -> Component.translatable("commands.deop.success", targets.iterator().next().getName()), true); ++ source.sendSuccess(() -> Component.translatable("commands.deop.success", gameProfile.getName()), true); // Paper - fixes MC-253721 + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch new file mode 100644 index 0000000000..dfb815a54e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -28,9 +28,13 @@ + GameType gameType = minecraftServer.getForcedGameType(); + if (gameType != null) { + for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { +- if (serverPlayer.setGameMode(gameType)) { +- i++; ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); ++ if (event != null && event.isCancelled()) { ++ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + } ++ // Paper end - Expand PlayerGameModeChangeEvent ++ i++; + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch new file mode 100644 index 0000000000..0898d7e23d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/server/commands/DifficultyCommand.java ++++ b/net/minecraft/server/commands/DifficultyCommand.java +@@ -44,11 +44,12 @@ + + public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException { + MinecraftServer minecraftserver = source.getServer(); ++ net.minecraft.server.level.ServerLevel worldServer = source.getLevel(); // CraftBukkit + +- if (minecraftserver.getWorldData().getDifficulty() == difficulty) { ++ if (worldServer.getDifficulty() == difficulty) { // CraftBukkit + throw DifficultyCommand.ERROR_ALREADY_DIFFICULT.create(difficulty.getKey()); + } else { +- minecraftserver.setDifficulty(difficulty, true); ++ minecraftserver.setDifficulty(worldServer, difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix) + source.sendSuccess(() -> { + return Component.translatable("commands.difficulty.success", difficulty.getDisplayName()); + }, true); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch new file mode 100644 index 0000000000..94c98f3597 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/server/commands/EffectCommands.java ++++ b/net/minecraft/server/commands/EffectCommands.java +@@ -84,7 +84,7 @@ + if (entity instanceof LivingEntity) { + MobEffectInstance mobeffect = new MobEffectInstance(statusEffect, k, amplifier, false, showParticles); + +- if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity())) { ++ if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit + ++j; + } + } +@@ -114,7 +114,7 @@ + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + +- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects()) { ++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit + ++i; + } + } +@@ -144,7 +144,7 @@ + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + +- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect)) { ++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit + ++i; + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch new file mode 100644 index 0000000000..5984de9d0f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/server/commands/GameModeCommand.java ++++ b/net/minecraft/server/commands/GameModeCommand.java +@@ -60,9 +60,14 @@ + int i = 0; + + for (ServerPlayer serverPlayer : targets) { +- if (serverPlayer.setGameMode(gameMode)) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); ++ if (event != null && !event.isCancelled()) { + logGamemodeChange(context.getSource(), serverPlayer, gameMode); + i++; ++ } else if (event != null && event.cancelMessage() != null) { ++ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); ++ // Paper end - Expand PlayerGameModeChangeEvent + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch new file mode 100644 index 0000000000..66f9df2b3b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/server/commands/GameRuleCommand.java ++++ b/net/minecraft/server/commands/GameRuleCommand.java +@@ -34,9 +34,9 @@ + + static > int setRule(CommandContext context, GameRules.Key key) { + CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); +- T t0 = commandlistenerwrapper.getServer().getGameRules().getRule(key); ++ T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit + +- t0.setFromArgument(context, "value"); ++ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent + commandlistenerwrapper.sendSuccess(() -> { + return Component.translatable("commands.gamerule.set", key.getId(), t0.toString()); + }, true); +@@ -44,7 +44,7 @@ + } + + static > int queryRule(CommandSourceStack source, GameRules.Key key) { +- T t0 = source.getServer().getGameRules().getRule(key); ++ T t0 = source.getLevel().getGameRules().getRule(key); // CraftBukkit + + source.sendSuccess(() -> { + return Component.translatable("commands.gamerule.query", key.getId(), t0.toString()); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch new file mode 100644 index 0000000000..025f1543d5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/server/commands/GiveCommand.java ++++ b/net/minecraft/server/commands/GiveCommand.java +@@ -38,6 +38,7 @@ + + private static int giveItem(CommandSourceStack source, ItemInput item, Collection targets, int count) throws CommandSyntaxException { + ItemStack itemstack = item.createItemStack(1, false); ++ final Component displayName = itemstack.getDisplayName(); // Paper - get display name early + int j = itemstack.getMaxStackSize(); + int k = j * 100; + +@@ -60,7 +61,7 @@ + ItemEntity entityitem; + + if (flag && itemstack1.isEmpty()) { +- entityitem = entityplayer.drop(itemstack, false); ++ entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event + if (entityitem != null) { + entityitem.makeFakeItem(); + } +@@ -79,11 +80,11 @@ + + if (targets.size() == 1) { + source.sendSuccess(() -> { +- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName()); ++ return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name + }, true); + } else { + source.sendSuccess(() -> { +- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size()); ++ return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name + }, true); + } + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch new file mode 100644 index 0000000000..8138965b1c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/KickCommand.java ++++ b/net/minecraft/server/commands/KickCommand.java +@@ -48,7 +48,7 @@ + + for (ServerPlayer serverPlayer : targets) { + if (!source.getServer().isSingleplayerOwner(serverPlayer.getGameProfile())) { +- serverPlayer.connection.disconnect(reason); ++ serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause + source.sendSuccess(() -> Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); + i++; + } diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch new file mode 100644 index 0000000000..93aec260e3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/server/commands/ListPlayersCommand.java ++++ b/net/minecraft/server/commands/ListPlayersCommand.java +@@ -35,7 +35,14 @@ + + private static int format(CommandSourceStack source, Function nameProvider) { + PlayerList playerlist = source.getServer().getPlayerList(); +- List list = playerlist.getPlayers(); ++ // CraftBukkit start ++ List players = playerlist.getPlayers(); ++ if (source.getBukkitSender() instanceof org.bukkit.entity.Player) { ++ org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender(); ++ players = players.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList()); ++ } ++ List list = players; ++ // CraftBukkit end + Component ichatbasecomponent = ComponentUtils.formatList(list, nameProvider); + + source.sendSuccess(() -> { diff --git a/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch new file mode 100644 index 0000000000..c579471341 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/server/commands/LootCommand.java ++++ b/net/minecraft/server/commands/LootCommand.java +@@ -95,7 +95,7 @@ + } + + private static > T addTargets(T rootArgument, LootCommand.TailProvider sourceConstructor) { +- return rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> { ++ return (T) rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> { // CraftBukkit - decompile error + return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), list.size(), list, commandloot_a); + }).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("count", IntegerArgumentType.integer(0)), (commandcontext, list, commandloot_a) -> { + return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), IntegerArgumentType.getInteger(commandcontext, "count"), list, commandloot_a); +@@ -250,6 +250,7 @@ + private static int dropInWorld(CommandSourceStack source, Vec3 pos, List stacks, LootCommand.Callback messageSender) throws CommandSyntaxException { + ServerLevel worldserver = source.getLevel(); + ++ stacks.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem + stacks.forEach((itemstack) -> { + ItemEntity entityitem = new ItemEntity(worldserver, pos.x, pos.y, pos.z, itemstack.copy()); + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch new file mode 100644 index 0000000000..3ca43d3777 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/OpCommand.java ++++ b/net/minecraft/server/commands/OpCommand.java +@@ -46,7 +46,7 @@ + if (!playerList.isOp(gameProfile)) { + playerList.op(gameProfile); + i++; +- source.sendSuccess(() -> Component.translatable("commands.op.success", targets.iterator().next().getName()), true); ++ source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfile.getName()), true); // Paper - fixes MC-253721 + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch new file mode 100644 index 0000000000..1c1e4cacb4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/server/commands/PlaceCommand.java ++++ b/net/minecraft/server/commands/PlaceCommand.java +@@ -132,6 +132,7 @@ + if (!structurestart.isValid()) { + throw PlaceCommand.ERROR_STRUCTURE_FAILED.create(); + } else { ++ structurestart.generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.COMMAND; // CraftBukkit - set AsyncStructureGenerateEvent.Cause.COMMAND as generation cause + BoundingBox structureboundingbox = structurestart.getBoundingBox(); + ChunkPos chunkcoordintpair = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.minX()), SectionPos.blockToSectionCoord(structureboundingbox.minZ())); + ChunkPos chunkcoordintpair1 = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.maxX()), SectionPos.blockToSectionCoord(structureboundingbox.maxZ())); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch new file mode 100644 index 0000000000..f0b9cdfcce --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/server/commands/ReloadCommand.java ++++ b/net/minecraft/server/commands/ReloadCommand.java +@@ -20,7 +20,7 @@ + public ReloadCommand() {} + + public static void reloadPacks(Collection dataPacks, CommandSourceStack source) { +- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { ++ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent + ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); + source.sendFailure(Component.translatable("commands.reload.failure")); + return null; +@@ -44,6 +44,16 @@ + return collection1; + } + ++ // CraftBukkit start ++ public static void reload(MinecraftServer minecraftserver) { ++ PackRepository resourcepackrepository = minecraftserver.getPackRepository(); ++ WorldData savedata = minecraftserver.getWorldData(); ++ Collection collection = resourcepackrepository.getSelectedIds(); ++ Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); ++ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent ++ } ++ // CraftBukkit end ++ + public static void register(CommandDispatcher dispatcher) { + dispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("reload").requires((commandlistenerwrapper) -> { + return commandlistenerwrapper.hasPermission(2); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch new file mode 100644 index 0000000000..885d99adba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/server/commands/ScheduleCommand.java ++++ b/net/minecraft/server/commands/ScheduleCommand.java +@@ -33,7 +33,7 @@ + }); + private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro")); + private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { +- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); ++ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world + }; + + public ScheduleCommand() {} +@@ -58,7 +58,7 @@ + } else { + long j = source.getLevel().getGameTime() + (long) time; + ResourceLocation minecraftkey = (ResourceLocation) function.getFirst(); +- TimerQueue customfunctioncallbacktimerqueue = source.getServer().getWorldData().overworldData().getScheduledEvents(); ++ TimerQueue customfunctioncallbacktimerqueue = source.getLevel().serverLevelData.overworldData().getScheduledEvents(); // CraftBukkit - SPIGOT-6667: Use world specific function timer + Optional> optional = ((Either) function.getSecond()).left(); + String s; + +@@ -93,7 +93,7 @@ + } + + private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException { +- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName); ++ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world + + if (i == 0) { + throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch new file mode 100644 index 0000000000..f4a5c32f80 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/server/commands/SetSpawnCommand.java ++++ b/net/minecraft/server/commands/SetSpawnCommand.java +@@ -38,24 +38,34 @@ + ResourceKey resourcekey = source.getLevel().dimension(); + Iterator iterator = targets.iterator(); + ++ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false); ++ // Paper start - Add PlayerSetSpawnEvent ++ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { ++ actualTargets.add(entityplayer); ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } ++ // Paper start - Add PlayerSetSpawnEvent ++ if (actualTargets.isEmpty()) { ++ return 0; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + + String s = resourcekey.location().toString(); + +- if (targets.size() == 1) { ++ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName()); ++ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent + }, true); + } else { + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size()); ++ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent + }, true); + } + +- return targets.size(); ++ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch new file mode 100644 index 0000000000..5e3b2e9493 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/SetWorldSpawnCommand.java ++++ b/net/minecraft/server/commands/SetWorldSpawnCommand.java +@@ -30,7 +30,7 @@ + private static int setSpawn(CommandSourceStack source, BlockPos pos, float angle) { + ServerLevel worldserver = source.getLevel(); + +- if (worldserver.dimension() != Level.OVERWORLD) { ++ if (false && worldserver.dimension() != Level.OVERWORLD) { // CraftBukkit - SPIGOT-7649: allow in all worlds + source.sendFailure(Component.translatable("commands.setworldspawn.failure.not_overworld")); + return 0; + } else { diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch new file mode 100644 index 0000000000..1827c81c69 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/server/commands/SpreadPlayersCommand.java ++++ b/net/minecraft/server/commands/SpreadPlayersCommand.java +@@ -93,7 +93,7 @@ + if (entity instanceof Player) { + set.add(entity.getTeam()); + } else { +- set.add((Object) null); ++ set.add((Team) null); // CraftBukkit - decompile error + } + } + +@@ -203,7 +203,7 @@ + commandspreadplayers_a = piles[j++]; + } + +- entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true); ++ entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); // CraftBukkit - handle teleport reason + d1 = Double.MAX_VALUE; + SpreadPlayersCommand.Position[] acommandspreadplayers_a1 = piles; + int k = piles.length; diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch new file mode 100644 index 0000000000..5a74f92631 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/server/commands/SummonCommand.java ++++ b/net/minecraft/server/commands/SummonCommand.java +@@ -57,6 +57,7 @@ + ServerLevel worldserver = source.getLevel(); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, EntitySpawnReason.COMMAND, (entity1) -> { + entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot()); ++ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason + return entity1; + }); + +@@ -67,7 +68,7 @@ + ((Mob) entity).finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, (SpawnGroupData) null); + } + +- if (!worldserver.tryAddFreshEntityWithPassengers(entity)) { ++ if (!worldserver.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND" + throw SummonCommand.ERROR_DUPLICATE_UUID.create(); + } else { + return entity; diff --git a/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch new file mode 100644 index 0000000000..0126e4d505 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/server/commands/TeleportCommand.java ++++ b/net/minecraft/server/commands/TeleportCommand.java +@@ -22,6 +22,7 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.network.chat.Component; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.LivingEntity; +@@ -30,6 +31,11 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++// CraftBukkit end + + public class TeleportCommand { + +@@ -167,7 +173,31 @@ + float f4 = Mth.wrapDegrees(f2); + float f5 = Mth.wrapDegrees(f3); + +- if (target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true)) { ++ // CraftBukkit start - Teleport event ++ boolean result; ++ if (target instanceof ServerPlayer player) { ++ result = player.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true, PlayerTeleportEvent.TeleportCause.COMMAND); ++ } else { ++ Location to = new Location(world.getWorld(), d3, d4, d5, f4, f5); ++ EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null) { // Paper ++ return; ++ } ++ to = event.getTo(); // Paper - actually track new location ++ ++ d3 = to.getX(); ++ d4 = to.getY(); ++ d5 = to.getZ(); ++ f4 = to.getYaw(); ++ f5 = to.getPitch(); ++ world = ((CraftWorld) to.getWorld()).getHandle(); ++ ++ result = target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true); ++ } ++ ++ if (result) { ++ // CraftBukkit end + if (facingLocation != null) { + facingLocation.perform(source, target); + } diff --git a/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch new file mode 100644 index 0000000000..52c7c73aa7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/server/commands/TimeCommand.java ++++ b/net/minecraft/server/commands/TimeCommand.java +@@ -8,6 +8,10 @@ + import net.minecraft.commands.arguments.TimeArgument; + import net.minecraft.network.chat.Component; + import net.minecraft.server.level.ServerLevel; ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.event.world.TimeSkipEvent; ++// CraftBukkit end + + public class TimeCommand { + +@@ -49,12 +53,18 @@ + } + + public static int setTime(CommandSourceStack source, int time) { +- Iterator iterator = source.getServer().getAllLevels().iterator(); ++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change + + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + +- worldserver.setDayTime((long) time); ++ // CraftBukkit start ++ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ worldserver.setDayTime((long) worldserver.getDayTime() + event.getSkipAmount()); ++ } ++ // CraftBukkit end + } + + source.getServer().forceTimeSynchronization(); +@@ -65,12 +75,18 @@ + } + + public static int addTime(CommandSourceStack source, int time) { +- Iterator iterator = source.getServer().getAllLevels().iterator(); ++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change + + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + +- worldserver.setDayTime(worldserver.getDayTime() + (long) time); ++ // CraftBukkit start ++ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ worldserver.setDayTime(worldserver.getDayTime() + event.getSkipAmount()); ++ } ++ // CraftBukkit end + } + + source.getServer().forceTimeSynchronization(); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch new file mode 100644 index 0000000000..2f916fdf48 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/server/commands/WeatherCommand.java ++++ b/net/minecraft/server/commands/WeatherCommand.java +@@ -34,11 +34,11 @@ + } + + private static int getDuration(CommandSourceStack source, int duration, IntProvider provider) { +- return duration == -1 ? provider.sample(source.getServer().overworld().getRandom()) : duration; ++ return duration == -1 ? provider.sample(source.getLevel().getRandom()) : duration; // CraftBukkit - SPIGOT-7680: per-world + } + + private static int setClear(CommandSourceStack source, int duration) { +- source.getServer().overworld().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false); ++ source.getLevel().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.clear"); + }, true); +@@ -46,7 +46,7 @@ + } + + private static int setRain(CommandSourceStack source, int duration) { +- source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false); ++ source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.rain"); + }, true); +@@ -54,7 +54,7 @@ + } + + private static int setThunder(CommandSourceStack source, int duration) { +- source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true); ++ source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.thunder"); + }, true); diff --git a/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch new file mode 100644 index 0000000000..d64cdd2c7d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch @@ -0,0 +1,65 @@ +--- a/net/minecraft/server/commands/WorldBorderCommand.java ++++ b/net/minecraft/server/commands/WorldBorderCommand.java +@@ -57,7 +57,7 @@ + } + + private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + + if (worldborder.getDamageSafeZone() == (double) distance) { + throw WorldBorderCommand.ERROR_SAME_DAMAGE_BUFFER.create(); +@@ -71,7 +71,7 @@ + } + + private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + + if (worldborder.getDamagePerBlock() == (double) damagePerBlock) { + throw WorldBorderCommand.ERROR_SAME_DAMAGE_AMOUNT.create(); +@@ -85,7 +85,7 @@ + } + + private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + + if (worldborder.getWarningTime() == time) { + throw WorldBorderCommand.ERROR_SAME_WARNING_TIME.create(); +@@ -99,7 +99,7 @@ + } + + private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + + if (worldborder.getWarningBlocks() == distance) { + throw WorldBorderCommand.ERROR_SAME_WARNING_DISTANCE.create(); +@@ -113,7 +113,7 @@ + } + + private static int getSize(CommandSourceStack source) { +- double d0 = source.getServer().overworld().getWorldBorder().getSize(); ++ double d0 = source.getLevel().getWorldBorder().getSize(); // CraftBukkit + + source.sendSuccess(() -> { + return Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", d0)); +@@ -122,7 +122,7 @@ + } + + private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + + if (worldborder.getCenterX() == (double) pos.x && worldborder.getCenterZ() == (double) pos.y) { + throw WorldBorderCommand.ERROR_SAME_CENTER.create(); +@@ -138,7 +138,7 @@ + } + + private static int setSize(CommandSourceStack source, double distance, long time) throws CommandSyntaxException { +- WorldBorder worldborder = source.getServer().overworld().getWorldBorder(); ++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit + double d1 = worldborder.getSize(); + + if (d1 == distance) { diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch new file mode 100644 index 0000000000..17d45a61ee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -18,6 +18,11 @@ + this.setViewDistance(dedicatedServerProperties.viewDistance); + this.setSimulationDistance(dedicatedServerProperties.simulationDistance); + super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); ++ // Paper start - fix converting txt to json file; moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end - fix converting txt to json file + this.loadUserBanList(); + this.saveUserBanList(); + this.loadIpBanList(); diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch new file mode 100644 index 0000000000..0780484bbb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -0,0 +1,475 @@ +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -54,20 +54,31 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; +-import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.SkullBlockEntity; + import net.minecraft.world.level.storage.LevelStorageSource; + import org.slf4j.Logger; + ++// CraftBukkit start ++import net.minecraft.server.WorldLoader; ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.io.IoBuilder; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.util.TerminalCompletionHandler; ++import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread; ++import org.bukkit.event.server.ServerCommandEvent; ++import org.bukkit.event.server.RemoteServerCommandEvent; ++// CraftBukkit end ++ + public class DedicatedServer extends MinecraftServer implements ServerInterface { + + static final Logger LOGGER = LogUtils.getLogger(); + private static final int CONVERSION_RETRY_DELAY_MS = 5000; + private static final int CONVERSION_RETRIES = 2; +- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); ++ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue + @Nullable + private QueryThreadGs4 queryThreadGs4; +- private final RconConsoleSource rconConsoleSource; ++ // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field + @Nullable + private RconThread rconThread; + public DedicatedServerSettings settings; +@@ -81,41 +92,117 @@ + private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker; + public ServerLinks serverLinks; + +- public DedicatedServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, DedicatedServerSettings propertiesLoader, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) { +- super(serverThread, session, dataPackManager, saveLoader, Proxy.NO_PROXY, dataFixer, apiServices, worldGenerationProgressListenerFactory); +- this.settings = propertiesLoader; +- this.rconConsoleSource = new RconConsoleSource(this); +- this.serverTextFilter = ServerTextFilter.createFromConfig(propertiesLoader.getProperties()); +- this.serverLinks = DedicatedServer.createServerLinks(propertiesLoader); ++ // CraftBukkit start - Signature changed ++ public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) { ++ super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory); ++ // CraftBukkit end ++ this.settings = dedicatedserversettings; ++ // this.rconConsoleSource = new RemoteControlCommandListener(this); // CraftBukkit - remove field ++ this.serverTextFilter = ServerTextFilter.createFromConfig(dedicatedserversettings.getProperties()); ++ this.serverLinks = DedicatedServer.createServerLinks(dedicatedserversettings); + } + + @Override + public boolean initServer() throws IOException { + Thread thread = new Thread("Server console handler") { + public void run() { +- BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.Main.useConsole) { ++ return; ++ } ++ // Paper start - Use TerminalConsoleAppender ++ new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); ++ /* ++ jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; + ++ // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return ++ try { ++ System.in.available(); ++ } catch (IOException ex) { ++ return; ++ } ++ // CraftBukkit end ++ + String s; + + try { +- while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (s = bufferedreader.readLine()) != null) { +- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack()); ++ // CraftBukkit start - JLine disabling compatibility ++ while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) { ++ if (org.bukkit.craftbukkit.Main.useJline) { ++ s = bufferedreader.readLine(">", null); ++ } else { ++ s = bufferedreader.readLine(); ++ } ++ ++ // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null ++ if (s == null) { ++ try { ++ Thread.sleep(50L); ++ } catch (InterruptedException ex) { ++ Thread.currentThread().interrupt(); ++ } ++ continue; ++ } ++ if (s.trim().length() > 0) { // Trim to filter lines which are just spaces ++ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); ++ } ++ // CraftBukkit end + } + } catch (IOException ioexception) { + DedicatedServer.LOGGER.error("Exception handling console input", ioexception); + } + ++ */ ++ // Paper end + } + }; + ++ // 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()); ++ ++ // Paper start - Not needed with TerminalConsoleAppender ++ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger(); ++ /* ++ 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); ++ } ++ } ++ ++ TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader); ++ this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler())); ++ writerThread.start(); ++ */ ++ // Paper end - Not needed with TerminalConsoleAppender ++ ++ System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream()); ++ System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream()); ++ // CraftBukkit end ++ + thread.setDaemon(true); + thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); +- thread.start(); ++ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down + DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); + if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); + } + ++ // Paper start - detect running as root ++ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { ++ DedicatedServer.LOGGER.warn("****************************"); ++ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); ++ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); ++ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); ++ DedicatedServer.LOGGER.warn("****************************"); ++ } ++ // Paper end - detect running as root ++ + DedicatedServer.LOGGER.info("Loading properties"); + DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); + +@@ -126,14 +213,51 @@ + this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections); + this.setLocalIp(dedicatedserverproperties.serverIp); + } ++ // Spigot start ++ this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); ++ org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); ++ org.spigotmc.SpigotConfig.registerCommands(); ++ // Spigot end ++ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. ++ // Paper start - initialize global and world-defaults configuration ++ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); ++ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); ++ // Paper end - initialize global and world-defaults configuration ++ this.server.spark.enableEarlyIfRequested(); // Paper - spark ++ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save ++ if (this.convertOldUsers()) { ++ this.getProfileCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end - fix converting txt to json file ++ org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread ++ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized ++ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command ++ this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark ++ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); + this.setMotd(dedicatedserverproperties.motd); + super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get()); + this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); +- this.worldData.setGameType(dedicatedserverproperties.gamemode); ++ // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading + DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress bindAddress; ++ if (this.getLocalIp().startsWith("unix:")) { ++ if (!io.netty.channel.epoll.Epoll.isAvailable()) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); ++ return false; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); ++ return false; ++ } ++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); ++ } else { + InetAddress inetaddress = null; + + if (!this.getLocalIp().isEmpty()) { +@@ -143,34 +267,55 @@ + if (this.getPort() < 0) { + this.setPort(dedicatedserverproperties.serverPort); + } ++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); ++ } ++ // Paper end - Unix domain socket support + + this.initializeKeyPair(); + DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); + + try { +- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); ++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support + } catch (IOException ioexception) { + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); + DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?"); ++ if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error + return false; + } + ++ // CraftBukkit start ++ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up ++ this.server.loadPlugins(); ++ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); ++ // CraftBukkit end ++ ++ // Paper start - Add Velocity IP Forwarding Support ++ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled; ++ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; ++ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; ++ // Paper end - Add Velocity IP Forwarding Support + if (!this.usesAuthentication()) { + DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); + DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); +- DedicatedServer.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."); ++ // Spigot start ++ // Paper start - Add Velocity IP Forwarding Support ++ if (usingProxy) { ++ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); ++ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information."); ++ // Paper end - Add Velocity IP Forwarding Support ++ } else { ++ DedicatedServer.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."); ++ } ++ // Spigot end + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertOldUsers()) { +- this.getProfileCache().save(); +- } + + if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { + return false; + } else { +- this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); ++ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // CraftBukkit - moved up + this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList()); + this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME); + long i = Util.getNanos(); +@@ -178,13 +323,13 @@ + SkullBlockEntity.setup(this.services, this); + GameProfileCache.setUsesAuthentication(this.usesAuthentication()); + DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName()); +- this.loadLevel(); ++ this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit + long j = Util.getNanos() - i; + String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D); + + DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); + if (dedicatedserverproperties.announcePlayerAchievements != null) { +- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this); ++ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world + } + + if (dedicatedserverproperties.enableQuery) { +@@ -197,7 +342,7 @@ + this.rconThread = RconThread.create(this); + } + +- if (this.getMaxTickLength() > 0L) { ++ if (false && this.getMaxTickLength() > 0L) { // Spigot - disable + Thread thread1 = new Thread(new ServerWatchdog(this)); + + thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER)); +@@ -215,6 +360,12 @@ + } + } + ++ // Paper start ++ public java.io.File getPluginsFolder() { ++ return (java.io.File) this.options.valueOf("plugins"); ++ } ++ // Paper end ++ + @Override + public boolean isSpawningMonsters() { + return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters(); +@@ -227,7 +378,7 @@ + + @Override + public void forceDifficulty() { +- this.setDifficulty(this.getProperties().difficulty, true); ++ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current + } + + @Override +@@ -286,13 +437,14 @@ + } + + if (this.rconThread != null) { +- this.rconThread.stop(); ++ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections + } + + if (this.queryThreadGs4 != null) { +- this.queryThreadGs4.stop(); ++ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections + } + ++ System.exit(0); // CraftBukkit + } + + @Override +@@ -302,19 +454,29 @@ + } + + @Override +- public boolean isLevelEnabled(Level world) { +- return world.dimension() == Level.NETHER ? this.getProperties().allowNether : true; ++ public boolean isLevelEnabled(net.minecraft.world.level.Level world) { ++ return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true; + } + + public void handleConsoleInput(String command, CommandSourceStack commandSource) { +- this.consoleInput.add(new ConsoleInput(command, commandSource)); ++ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue + } + + public void handleConsoleInputs() { +- while (!this.consoleInput.isEmpty()) { +- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); ++ // Paper start - Perf: use proper queue ++ ConsoleInput servercommand; ++ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ // Paper end - Perf: use proper queue + +- this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); ++ // CraftBukkit start - ServerCommand for preprocessing ++ ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg); ++ this.server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) continue; ++ servercommand = new ConsoleInput(event.getCommand(), servercommand.source); ++ ++ // this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); // Called in dispatchServerCommand ++ this.server.dispatchServerCommand(this.console, servercommand); ++ // CraftBukkit end + } + + } +@@ -383,7 +545,7 @@ + + @Override + public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) { +- if (world.dimension() != Level.OVERWORLD) { ++ if (world.dimension() != net.minecraft.world.level.Level.OVERWORLD) { + return false; + } else if (this.getPlayerList().getOps().isEmpty()) { + return false; +@@ -453,7 +615,11 @@ + public boolean enforceSecureProfile() { + DedicatedServerProperties dedicatedserverproperties = this.getProperties(); + +- return dedicatedserverproperties.enforceSecureProfile && dedicatedserverproperties.onlineMode && this.services.canValidateProfileKeys(); ++ // Paper start - Add setting for proxy online mode status ++ return dedicatedserverproperties.enforceSecureProfile ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ++ && this.services.canValidateProfileKeys(); ++ // Paper end - Add setting for proxy online mode status + } + + @Override +@@ -541,16 +707,52 @@ + + @Override + public String getPluginNames() { +- return ""; ++ // CraftBukkit start - Whole method ++ StringBuilder result = new StringBuilder(); ++ org.bukkit.plugin.Plugin[] plugins = this.server.getPluginManager().getPlugins(); ++ ++ result.append(this.server.getName()); ++ result.append(" on Bukkit "); ++ result.append(this.server.getBukkitVersion()); ++ ++ if (plugins.length > 0 && this.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 + } + + @Override + public String runCommand(String command) { +- this.rconConsoleSource.prepareForCommand(); ++ // CraftBukkit start - fire RemoteServerCommandEvent ++ throw new UnsupportedOperationException("Not supported - remote source required."); ++ } ++ ++ public String runCommand(RconConsoleSource rconConsoleSource, String s) { ++ rconConsoleSource.prepareForCommand(); + this.executeBlocking(() -> { +- this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command); ++ CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack(); ++ RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s); ++ this.server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper); ++ this.server.dispatchServerCommand(event.getSender(), serverCommand); + }); +- return this.rconConsoleSource.getCommandResponse(); ++ return rconConsoleSource.getCommandResponse(); ++ // CraftBukkit end + } + + public void storeUsingWhiteList(boolean useWhitelist) { +@@ -660,4 +862,15 @@ + } + } + } ++ ++ // CraftBukkit start ++ public boolean isDebugging() { ++ return this.getProperties().debug; ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return this.console; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch new file mode 100644 index 0000000000..2020814765 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch @@ -0,0 +1,106 @@ +--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -43,11 +43,16 @@ + import net.minecraft.world.level.levelgen.presets.WorldPresets; + import org.slf4j.Logger; + ++// CraftBukkit start ++import joptsimple.OptionSet; ++// CraftBukkit end ++ + public class DedicatedServerProperties extends Settings { + + static final Logger LOGGER = LogUtils.getLogger(); + private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$"); + private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults(); ++ public final boolean debug = this.get("debug", false); // CraftBukkit + public final boolean onlineMode = this.get("online-mode", true); + public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false); + public final String serverIp = this.get("server-ip", ""); +@@ -100,13 +105,17 @@ + public final Settings.MutableValue whiteList; + public final boolean enforceSecureProfile; + public final boolean logIPs; +- public final int pauseWhenEmptySeconds; ++ public int pauseWhenEmptySeconds; + private final DedicatedServerProperties.WorldDimensionData worldDimensionData; + public final WorldOptions worldOptions; + public boolean acceptsTransfers; + +- public DedicatedServerProperties(Properties properties) { +- super(properties); ++ public final String rconIp; // Paper - Configurable rcon ip ++ ++ // CraftBukkit start ++ public DedicatedServerProperties(Properties properties, OptionSet optionset) { ++ super(properties, optionset); ++ // CraftBukkit end + this.difficulty = (Difficulty) this.get("difficulty", dispatchNumberOrString(Difficulty::byId, Difficulty::byName), Difficulty::getKey, Difficulty.EASY); + this.gamemode = (GameType) this.get("gamemode", dispatchNumberOrString(GameType::byId, GameType::byName), GameType::getName, GameType.SURVIVAL); + this.levelName = this.get("level-name", "world"); +@@ -137,7 +146,7 @@ + this.maxWorldSize = this.get("max-world-size", (integer) -> { + return Mth.clamp(integer, 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true); ++ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag + this.regionFileComression = this.get("region-file-compression", "deflate"); + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); +@@ -151,7 +160,7 @@ + this.whiteList = this.getMutable("white-list", false); + this.enforceSecureProfile = this.get("enforce-secure-profile", true); + this.logIPs = this.get("log-ips", true); +- this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", 60); ++ this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", -1); // Paper - disable tick sleeping by default + this.acceptsTransfers = this.get("accepts-transfers", false); + String s = this.get("level-seed", ""); + boolean flag = this.get("generate-structures", true); +@@ -165,15 +174,21 @@ + }, WorldPresets.NORMAL.location().toString())); + this.serverResourcePackInfo = DedicatedServerProperties.getServerPackInfo(this.get("resource-pack-id", ""), this.get("resource-pack", ""), this.get("resource-pack-sha1", ""), this.getLegacyString("resource-pack-hash"), this.get("require-resource-pack", false), this.get("resource-pack-prompt", "")); + this.initialDataPackConfiguration = DedicatedServerProperties.getDatapackConfig(this.get("initial-enabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getEnabled())), this.get("initial-disabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getDisabled()))); ++ // Paper start - Configurable rcon ip ++ final String rconIp = this.getStringRaw("rcon.ip"); ++ this.rconIp = rconIp == null ? this.serverIp : rconIp; ++ // Paper end - Configurable rcon ip + } + +- public static DedicatedServerProperties fromFile(Path path) { +- return new DedicatedServerProperties(loadFromFile(path)); ++ // CraftBukkit start ++ public static DedicatedServerProperties fromFile(Path path, OptionSet optionset) { ++ return new DedicatedServerProperties(loadFromFile(path), optionset); + } + + @Override +- protected DedicatedServerProperties reload(RegistryAccess registryManager, Properties properties) { +- return new DedicatedServerProperties(properties); ++ public DedicatedServerProperties reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset) { ++ return new DedicatedServerProperties(properties, optionset); ++ // CraftBukkit end + } + + @Nullable +@@ -254,10 +269,10 @@ + }).orElseThrow(() -> { + return new IllegalStateException("Invalid datapack contents: can't find default preset"); + }); +- Optional optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> { ++ Optional> optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> { // CraftBukkit - decompile error + return ResourceKey.create(Registries.WORLD_PRESET, minecraftkey); + }).or(() -> { +- return Optional.ofNullable((ResourceKey) DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType)); ++ return Optional.ofNullable(DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType)); // CraftBukkit - decompile error + }); + + Objects.requireNonNull(holderlookup); +@@ -269,7 +284,7 @@ + + if (holder.is(WorldPresets.FLAT)) { + RegistryOps registryops = registries.createSerializationContext(JsonOps.INSTANCE); +- DataResult dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings())); ++ DataResult dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings())); // CraftBukkit - decompile error + Logger logger = DedicatedServerProperties.LOGGER; + + Objects.requireNonNull(logger); diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch new file mode 100644 index 0000000000..0aaaffd34b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/server/dedicated/DedicatedServerSettings.java ++++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java +@@ -3,14 +3,21 @@ + import java.nio.file.Path; + import java.util.function.UnaryOperator; + ++// CraftBukkit start ++import java.io.File; ++import joptsimple.OptionSet; ++// CraftBukkit end ++ + public class DedicatedServerSettings { + + private final Path source; + private DedicatedServerProperties properties; + +- public DedicatedServerSettings(Path path) { +- this.source = path; +- this.properties = DedicatedServerProperties.fromFile(path); ++ // CraftBukkit start ++ public DedicatedServerSettings(OptionSet optionset) { ++ this.source = ((File) optionset.valueOf("config")).toPath(); ++ this.properties = DedicatedServerProperties.fromFile(this.source, optionset); ++ // CraftBukkit end + } + + public DedicatedServerProperties getProperties() { diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch new file mode 100644 index 0000000000..300eeaa029 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch @@ -0,0 +1,179 @@ +--- a/net/minecraft/server/dedicated/Settings.java ++++ b/net/minecraft/server/dedicated/Settings.java +@@ -20,20 +20,41 @@ + import java.util.function.Supplier; + import java.util.function.UnaryOperator; + import javax.annotation.Nullable; +-import net.minecraft.core.RegistryAccess; + import org.slf4j.Logger; + ++import joptsimple.OptionSet; // CraftBukkit ++import net.minecraft.core.RegistryAccess; ++ + public abstract class Settings> { + + private static final Logger LOGGER = LogUtils.getLogger(); + public final Properties properties; ++ private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments ++ // CraftBukkit start ++ private OptionSet options = null; + +- public Settings(Properties properties) { ++ public Settings(Properties properties, final OptionSet options) { + this.properties = properties; ++ ++ this.options = options; + } + ++ private String getOverride(String name, String value) { ++ if ((this.options != null) && (this.options.has(name))) { ++ return String.valueOf(this.options.valueOf(name)); ++ } ++ ++ return value; ++ // CraftBukkit end ++ } ++ + public static Properties loadFromFile(Path path) { + try { ++ // CraftBukkit start - SPIGOT-7465, MC-264979: Don't load if file doesn't exist ++ if (!path.toFile().exists()) { ++ return new Properties(); ++ } ++ // CraftBukkit end + Properties properties; + Properties properties1; + +@@ -97,8 +118,53 @@ + + public void store(Path path) { + try { +- BufferedWriter bufferedwriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8); ++ // CraftBukkit start - Don't attempt writing to file if it's read only ++ if (path.toFile().exists() && !path.toFile().canWrite()) { ++ Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper - log message file is read-only ++ return; ++ } ++ // CraftBukkit end ++ // Paper start - allow skipping server.properties comments ++ java.io.OutputStream outputstream = Files.newOutputStream(path); ++ java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) { ++ private boolean isRightAfterNewline = true; // If last written char was newline ++ private boolean isComment = false; // Are we writing comment currently? ++ ++ @Override ++ public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException { ++ this.write(b, 0, b.length); ++ } ++ ++ @Override ++ public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException { ++ int latest_offset = off; // The latest offset, updated when comment ends ++ for (int index = off; index < off + len; ++index ) { ++ byte c = bbuf[index]; ++ boolean isNewline = (c == '\n' || c == '\r'); ++ if (isNewline && this.isComment) { ++ // Comment has ended ++ this.isComment = false; ++ latest_offset = index+1; ++ } ++ if (c == '#' && this.isRightAfterNewline) { ++ this.isComment = true; ++ if (index != latest_offset) { ++ // We got some non-comment data earlier ++ super.write(bbuf, latest_offset, index-latest_offset); ++ } ++ } ++ this.isRightAfterNewline = isNewline; // Store for next iteration + ++ } ++ if (latest_offset < off+len && !this.isComment) { ++ // We have some unwritten data, that isn't part of a comment ++ super.write(bbuf, latest_offset, (off + len) - latest_offset); ++ } ++ } ++ }; ++ BufferedWriter bufferedwriter = new BufferedWriter(new java.io.OutputStreamWriter(bufferedOutputStream, java.nio.charset.StandardCharsets.UTF_8.newEncoder())); ++ // Paper end - allow skipping server.properties comments ++ + try { + this.properties.store(bufferedwriter, "Minecraft server properties"); + } catch (Throwable throwable) { +@@ -125,7 +191,7 @@ + private static Function wrapNumberDeserializer(Function parser) { + return (s) -> { + try { +- return (Number) parser.apply(s); ++ return (V) parser.apply(s); // CraftBukkit - decompile error + } catch (NumberFormatException numberformatexception) { + return null; + } +@@ -144,7 +210,7 @@ + + @Nullable + public String getStringRaw(String key) { +- return (String) this.properties.get(key); ++ return (String) this.getOverride(key, this.properties.getProperty(key)); // CraftBukkit + } + + @Nullable +@@ -160,10 +226,20 @@ + } + + protected V get(String key, Function parser, Function stringifier, V fallback) { +- String s1 = this.getStringRaw(key); +- V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback); ++ // CraftBukkit start ++ try { ++ return this.get0(key, parser, stringifier, fallback); ++ } catch (Exception ex) { ++ throw new RuntimeException("Could not load invalidly configured property '" + key + "'", ex); ++ } ++ } + +- this.properties.put(key, stringifier.apply(v1)); ++ private V get0(String s, Function function, Function function1, V v0) { ++ // CraftBukkit end ++ String s1 = this.getStringRaw(s); ++ V v1 = MoreObjects.firstNonNull(s1 != null ? function.apply(s1) : null, v0); ++ ++ this.properties.put(s, function1.apply(v1)); + return v1; + } + +@@ -172,7 +248,7 @@ + V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback); + + this.properties.put(key, stringifier.apply(v1)); +- return new Settings.MutableValue<>(key, v1, stringifier); ++ return new Settings.MutableValue(key, v1, stringifier); // CraftBukkit - decompile error + } + + protected V get(String key, Function parser, UnaryOperator parsedTransformer, Function stringifier, V fallback) { +@@ -236,7 +312,7 @@ + return properties; + } + +- protected abstract T reload(RegistryAccess registryManager, Properties properties); ++ protected abstract T reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset); // CraftBukkit + + public class MutableValue implements Supplier { + +@@ -244,7 +320,7 @@ + private final V value; + private final Function serializer; + +- MutableValue(final String s, final Object object, final Function function) { ++ MutableValue(final String s, final V object, final Function function) { // CraftBukkit - decompile error + this.key = s; + this.value = object; + this.serializer = function; +@@ -258,7 +334,7 @@ + Properties properties = Settings.this.cloneProperties(); + + properties.put(this.key, this.serializer.apply(value)); +- return Settings.this.reload(registryManager, properties); ++ return Settings.this.reload(registryManager, properties, Settings.this.options); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch b/paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch new file mode 100644 index 0000000000..84332717d5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch @@ -0,0 +1,103 @@ +--- a/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/net/minecraft/server/gui/MinecraftServerGui.java +@@ -59,6 +59,15 @@ + jframe.pack(); + jframe.setLocationRelativeTo((Component) null); + jframe.setVisible(true); ++ jframe.setName("Minecraft server"); // Paper - Improve ServerGUI ++ ++ // Paper start - Improve ServerGUI ++ try { ++ jframe.setIconImage(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); ++ } catch (java.io.IOException ignore) { ++ } ++ // Paper end - Improve ServerGUI ++ + jframe.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowevent) { + if (!servergui.isClosing.getAndSet(true)) { +@@ -81,6 +90,7 @@ + this.setLayout(new BorderLayout()); + + try { ++ this.add(this.buildOnboardingPanel(), "North"); // Paper - Add onboarding message for initial server start + this.add(this.buildChatPanel(), "Center"); + this.add(this.buildInfoPanel(), "West"); + } catch (Exception exception) { +@@ -95,8 +105,8 @@ + + private JComponent buildInfoPanel() { + JPanel jpanel = new JPanel(new BorderLayout()); +- StatsComponent guistatscomponent = new StatsComponent(this.server); +- Collection collection = this.finalizers; ++ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Make GUI graph fancier ++ Collection collection = this.finalizers; // CraftBukkit - decompile error + + Objects.requireNonNull(guistatscomponent); + collection.add(guistatscomponent::close); +@@ -106,6 +116,39 @@ + return jpanel; + } + ++ // Paper start - Add onboarding message for initial server start ++ private JComponent buildOnboardingPanel() { ++ String onboardingLink = "https://docs.papermc.io/paper/next-steps"; ++ JPanel jPanel = new JPanel(); ++ ++ javax.swing.JLabel jLabel = new javax.swing.JLabel("If you need help setting up your server you can visit:"); ++ jLabel.setFont(MinecraftServerGui.MONOSPACED); ++ ++ javax.swing.JLabel link = new javax.swing.JLabel(" " + onboardingLink + ""); ++ link.setFont(MinecraftServerGui.MONOSPACED); ++ link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); ++ link.addMouseListener(new java.awt.event.MouseAdapter() { ++ @Override ++ public void mouseClicked(final java.awt.event.MouseEvent e) { ++ try { ++ java.awt.Desktop.getDesktop().browse(java.net.URI.create(onboardingLink)); ++ } catch (java.io.IOException exception) { ++ LOGGER.error("Unable to find a default browser. Please manually visit the website: " + onboardingLink, exception); ++ } catch (UnsupportedOperationException exception) { ++ LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: " + onboardingLink, exception); ++ } catch (SecurityException exception) { ++ LOGGER.error("This action has been denied by the security manager. Please manually visit the website: " + onboardingLink, exception); ++ } ++ } ++ }); ++ ++ jPanel.add(jLabel); ++ jPanel.add(link); ++ ++ return jPanel; ++ } ++ // Paper end - Add onboarding message for initial server start ++ + private JComponent buildPlayerPanel() { + JList jlist = new PlayerListComponent(this.server); + JScrollPane jscrollpane = new JScrollPane(jlist, 22, 30); +@@ -132,7 +175,7 @@ + + jtextfield.setText(""); + }); +- jtextarea.addFocusListener(new FocusAdapter(this) { ++ jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error + public void focusGained(FocusEvent focusevent) {} + }); + jpanel.add(jscrollpane, "Center"); +@@ -166,6 +209,7 @@ + this.finalizers.forEach(Runnable::run); + } + ++ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper + public void print(JTextArea textArea, JScrollPane scrollPane, String message) { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> { +@@ -181,7 +225,7 @@ + } + + try { +- document.insertString(document.getLength(), message, (AttributeSet) null); ++ document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit + } catch (BadLocationException badlocationexception) { + ; + } diff --git a/paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch b/paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch new file mode 100644 index 0000000000..3fa5e0162c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/server/gui/StatsComponent.java ++++ b/net/minecraft/server/gui/StatsComponent.java +@@ -34,10 +34,19 @@ + + private void tick() { + long l = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); ++ // Paper start - Improve ServerGUI ++ double[] tps = org.bukkit.Bukkit.getTPS(); ++ String[] tpsAvg = new String[tps.length]; ++ ++ for ( int g = 0; g < tps.length; g++) { ++ tpsAvg[g] = format( tps[g] ); ++ } + this.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; + this.msgs[1] = "Avg tick: " + + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND) + + " ms"; ++ this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); ++ // Paper end - Improve ServerGUI + this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); + this.repaint(); + } +@@ -66,4 +75,10 @@ + public void close() { + this.timer.stop(); + } ++ ++ // Paper start - Improve ServerGUI ++ private static String format(double tps) { ++ return (( tps > 21.0 ) ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ } ++ // Paper end - Improve ServerGUI + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch new file mode 100644 index 0000000000..4516416d44 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch @@ -0,0 +1,245 @@ +--- a/net/minecraft/server/level/ChunkHolder.java ++++ b/net/minecraft/server/level/ChunkHolder.java +@@ -28,14 +28,18 @@ + import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.level.lighting.LevelLightEngine; + ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; ++// CraftBukkit end ++ + public class ChunkHolder extends GenerationChunkHolder { + + public static final ChunkResult UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk"); + private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); + private final LevelHeightAccessor levelHeightAccessor; +- private volatile CompletableFuture> fullChunkFuture; +- private volatile CompletableFuture> tickingChunkFuture; +- private volatile CompletableFuture> entityTickingChunkFuture; ++ private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage ++ private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage + public int oldTicketLevel; + private int ticketLevel; + private int queueLevel; +@@ -58,9 +62,9 @@ + this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.blockChangedLightSectionFilter = new BitSet(); + this.skyChangedLightSectionFilter = new BitSet(); +- this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null); +- this.sendSync = CompletableFuture.completedFuture((Object) null); +- this.saveSync = CompletableFuture.completedFuture((Object) null); ++ this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error + this.levelHeightAccessor = world; + this.lightEngine = lightingProvider; + this.onLevelChange = levelUpdateListener; +@@ -72,6 +76,18 @@ + this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; + } + ++ // CraftBukkit start ++ public LevelChunk getFullChunkNow() { ++ // Note: We use the oldTicketLevel for isLoaded checks. ++ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null; ++ return this.getFullChunkNowUnchecked(); ++ } ++ ++ public LevelChunk getFullChunkNowUnchecked() { ++ return (LevelChunk) this.getChunkIfPresentUnchecked(ChunkStatus.FULL); ++ } ++ // CraftBukkit end ++ + public CompletableFuture> getTickingChunkFuture() { + return this.tickingChunkFuture; + } +@@ -85,8 +101,8 @@ + } + + @Nullable +- public LevelChunk getTickingChunk() { +- return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse((Object) null); ++ public final LevelChunk getTickingChunk() { // Paper - final for inline ++ return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error + } + + @Nullable +@@ -138,6 +154,7 @@ + boolean flag = this.hasChangedSections; + int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); + ++ if (i < 0 || i >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 + if (this.changedBlocksPerSection[i] == null) { + this.hasChangedSections = true; + this.changedBlocksPerSection[i] = new ShortOpenHashSet(); +@@ -224,8 +241,11 @@ + ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection); + + this.broadcast(list, packetplayoutmultiblockchange); ++ // CraftBukkit start ++ List finalList = list; + packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> { +- this.broadcastBlockEntityIfNeeded(list, world, blockposition1, iblockdata1); ++ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1); ++ // CraftBukkit end + }); + } + } +@@ -291,7 +311,7 @@ + this.pendingFullStateConfirmation = completablefuture1; + chunkFuture.thenAccept((chunkresult) -> { + chunkresult.ifSuccess((chunk) -> { +- completablefuture1.complete((Object) null); ++ completablefuture1.complete(null); // CraftBukkit - decompile error + }); + }); + } +@@ -301,6 +321,38 @@ + chunkLoadingManager.onFullChunkStatusChange(this.pos, target); + } + ++ // CraftBukkit start ++ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. ++ // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks ++ protected void callEventIfUnloading(ChunkMap playerchunkmap) { ++ FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel); ++ FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel); ++ boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL); ++ boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL); ++ if (oldIsFull && !newIsFull) { ++ this.getFullChunkFuture().thenAccept((either) -> { ++ LevelChunk chunk = (LevelChunk) either.orElse(null); ++ if (chunk != null) { ++ playerchunkmap.callbackExecutor.execute(() -> { ++ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick ++ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. ++ // These actions may however happen deferred, so we manually set the needsSaving flag already here. ++ chunk.markUnsaved(); ++ chunk.unloadCallback(); ++ }); ++ } ++ }).exceptionally((throwable) -> { ++ // ensure exceptions are printed, by default this is not the case ++ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); ++ return null; ++ }); ++ ++ // Run callback right away if the future was already done ++ playerchunkmap.callbackExecutor.run(); ++ } ++ } ++ // CraftBukkit end ++ + protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) { + FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); + FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); +@@ -309,12 +361,28 @@ + + this.wasAccessibleSinceLastSave |= flag1; + if (!flag && flag1) { ++ int expectCreateCount = ++this.fullChunkCreateCount; // Paper + this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this); + this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL); ++ // Paper start - cache ticking ready status ++ this.fullChunkFuture.thenAccept(chunkResult -> { ++ chunkResult.ifSuccess(chunk -> { ++ if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { ++ ChunkHolder.this.isFullChunkReady = true; ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this); ++ } ++ }); ++ }); ++ // Paper end - cache ticking ready status + this.addSaveDependency(this.fullChunkFuture); + } + + if (flag && !flag1) { ++ // Paper start ++ if (this.isFullChunkReady) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper ++ } ++ // Paper end + this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } +@@ -325,11 +393,25 @@ + if (!flag2 && flag3) { + this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this); + this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); ++ // Paper start - cache ticking ready status ++ this.tickingChunkFuture.thenAccept(chunkResult -> { ++ chunkResult.ifSuccess(chunk -> { ++ // note: Here is a very good place to add callbacks to logic waiting on this. ++ ChunkHolder.this.isTickingReady = true; ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this); ++ }); ++ }); ++ // Paper end + this.addSaveDependency(this.tickingChunkFuture); + } + + if (flag2 && !flag3) { +- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); ++ // Paper start ++ if (this.isTickingReady) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper ++ } ++ // Paper end ++ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage + this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } + +@@ -343,11 +425,24 @@ + + this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this); + this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); ++ // Paper start - cache ticking ready status ++ this.entityTickingChunkFuture.thenAccept(chunkResult -> { ++ chunkResult.ifSuccess(chunk -> { ++ ChunkHolder.this.isEntityTickingReady = true; ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this); ++ }); ++ }); ++ // Paper end + this.addSaveDependency(this.entityTickingChunkFuture); + } + + if (flag4 && !flag5) { +- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); ++ // Paper start ++ if (this.isEntityTickingReady) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); ++ } ++ // Paper end ++ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + } + +@@ -357,6 +452,26 @@ + + this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); + this.oldTicketLevel = this.ticketLevel; ++ // CraftBukkit start ++ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. ++ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { ++ this.getFullChunkFuture().thenAccept((either) -> { ++ LevelChunk chunk = (LevelChunk) either.orElse(null); ++ if (chunk != null) { ++ chunkLoadingManager.callbackExecutor.execute(() -> { ++ chunk.loadCallback(); ++ }); ++ } ++ }).exceptionally((throwable) -> { ++ // ensure exceptions are printed, by default this is not the case ++ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); ++ return null; ++ }); ++ ++ // Run callback right away if the future was already done ++ chunkLoadingManager.callbackExecutor.run(); ++ } ++ // CraftBukkit end + } + + public boolean wasAccessibleSinceLastSave() { diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch new file mode 100644 index 0000000000..9a5c5895b8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -0,0 +1,433 @@ +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -104,6 +104,10 @@ + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.generator.CustomChunkGenerator; ++// CraftBukkit end ++ + public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap { + + private static final ChunkResult> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range"); +@@ -149,6 +153,33 @@ + public int serverViewDistance; + private final WorldGenContext worldGenContext; + ++ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() ++ public final CallbackExecutor callbackExecutor = new CallbackExecutor(); ++ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { ++ ++ private final java.util.Queue queue = new java.util.ArrayDeque<>(); ++ ++ @Override ++ public void execute(Runnable runnable) { ++ this.queue.add(runnable); ++ } ++ ++ @Override ++ public void run() { ++ Runnable task; ++ while ((task = this.queue.poll()) != null) { ++ task.run(); ++ } ++ } ++ }; ++ // CraftBukkit end ++ ++ // Paper start ++ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { ++ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ // Paper end ++ + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); + this.visibleChunkMap = this.updatingChunkMap.clone(); +@@ -170,13 +201,19 @@ + RegistryAccess iregistrycustom = world.registryAccess(); + long j = world.getSeed(); + +- if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) { ++ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random. ++ ChunkGenerator randomGenerator = chunkGenerator; ++ if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) { ++ randomGenerator = customChunkGenerator.getDelegate(); ++ } ++ if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) { ++ // CraftBukkit end + this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j); + } else { + this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j); + } + +- this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j); ++ this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot + this.mainThreadExecutor = mainThreadExecutor; + ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen"); + +@@ -198,6 +235,12 @@ + this.chunksToEagerlySave.add(pos.toLong()); + } + ++ // Paper start ++ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { ++ return -1; ++ } ++ // Paper end ++ + protected ChunkGenerator generator() { + return this.worldGenContext.generator(); + } +@@ -325,7 +368,7 @@ + throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); + } + +- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null); ++ ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error + + if (ichunkaccess == null) { + return ChunkMap.UNLOADED_CHUNK_LIST_RESULT; +@@ -354,9 +397,9 @@ + }; + + stringbuilder.append("Updating:").append(System.lineSeparator()); +- this.updatingChunkMap.values().forEach(consumer); ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper + stringbuilder.append("Visible:").append(System.lineSeparator()); +- this.visibleChunkMap.values().forEach(consumer); ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper + CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading"); + +@@ -398,6 +441,9 @@ + holder.setTicketLevel(level); + } else { + holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this); ++ // Paper start ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder); ++ // Paper end + } + + this.updatingChunkMap.put(pos, holder); +@@ -427,7 +473,7 @@ + + protected void saveAllChunks(boolean flush) { + if (flush) { +- List list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); ++ List list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper + MutableBoolean mutableboolean = new MutableBoolean(); + + do { +@@ -453,7 +499,7 @@ + } else { + this.nextChunkSaveTime.clear(); + long i = Util.getMillis(); +- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator(); ++ Iterator objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper + + while (objectiterator.hasNext()) { + ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); +@@ -478,7 +524,7 @@ + } + + public boolean hasWork() { +- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); ++ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); + } + + private void processUnloads(BooleanSupplier shouldKeepTicking) { +@@ -537,8 +583,11 @@ + this.scheduleUnload(pos, chunk); + } else { + ChunkAccess ichunkaccess = chunk.getLatestChunk(); +- +- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) { ++ // Paper start ++ boolean removed; ++ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); ++ // Paper end + LevelChunk chunk1; + + if (ichunkaccess instanceof LevelChunk) { +@@ -556,7 +605,9 @@ + this.lightEngine.tryScheduleUpdate(); + this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); + this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong()); +- } ++ } else if (removed) { // Paper start ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); ++ } // Paper end + + } + }; +@@ -905,7 +956,7 @@ + } + } + +- protected void setServerViewDistance(int watchDistance) { ++ public void setServerViewDistance(int watchDistance) { // Paper - public + int j = Mth.clamp(watchDistance, 2, 32); + + if (j != this.serverViewDistance) { +@@ -922,7 +973,7 @@ + + } + +- int getPlayerViewDistance(ServerPlayer player) { ++ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public + return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); + } + +@@ -951,7 +1002,7 @@ + } + + public int size() { +- return this.visibleChunkMap.size(); ++ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper + } + + public DistanceManager getDistanceManager() { +@@ -959,25 +1010,26 @@ + } + + protected Iterable getChunks() { +- return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); ++ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper + } + + void dumpChunks(Writer writer) throws IOException { + CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); + TickingTracker tickingtracker = this.distanceManager.tickingTracker(); +- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator(); ++ Iterator objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper + + while (objectbidirectionaliterator.hasNext()) { +- Entry entry = (Entry) objectbidirectionaliterator.next(); +- long i = entry.getLongKey(); ++ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper ++ long i = playerchunk.pos.toLong(); // Paper + ChunkPos chunkcoordintpair = new ChunkPos(i); +- ChunkHolder playerchunk = (ChunkHolder) entry.getValue(); ++ // Paper - move up + Optional optional = Optional.ofNullable(playerchunk.getLatestChunk()); + Optional optional1 = optional.flatMap((ichunkaccess) -> { + return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); + }); + +- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { ++ // CraftBukkit - decompile error ++ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { + return chunk.getBlockEntities().size(); + }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { + return chunk.getBlockTicks().count(); +@@ -990,7 +1042,7 @@ + + private static String printFuture(CompletableFuture> future) { + try { +- ChunkResult chunkresult = (ChunkResult) future.getNow((Object) null); ++ ChunkResult chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error + + return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed"; + } catch (CompletionException completionexception) { +@@ -1002,12 +1054,14 @@ + + private CompletableFuture> readChunk(ChunkPos chunkPos) { + return this.read(chunkPos).thenApplyAsync((optional) -> { +- return optional.map(this::upgradeChunkTag); ++ return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit + }, Util.backgroundExecutor().forName("upgradeChunk")); + } + +- private CompoundTag upgradeChunkTag(CompoundTag nbt) { +- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer()); ++ // CraftBukkit start ++ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) { ++ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level); ++ // CraftBukkit end + } + + void forEachSpawnCandidateChunk(Consumer callback) { +@@ -1025,10 +1079,23 @@ + } + + public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { +- return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos); ++ // Spigot start ++ return this.anyPlayerCloseEnoughForSpawning(pos, false); + } + ++ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange); ++ // Spigot end ++ } ++ + private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) { ++ // Spigot start ++ return this.anyPlayerCloseEnoughForSpawningInternal(pos, false); ++ } ++ ++ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ double blockRange; // Paper - use from event ++ // Spigot end + Iterator iterator = this.playerMap.getAllPlayers().iterator(); + + ServerPlayer entityplayer; +@@ -1039,7 +1106,16 @@ + } + + entityplayer = (ServerPlayer) iterator.next(); +- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos)); ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ blockRange = 16384.0D; ++ if (reducedRange) { ++ event = entityplayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) continue; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent ++ } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot + + return true; + } +@@ -1056,7 +1132,7 @@ + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) { ++ if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot + builder.add(entityplayer); + } + } +@@ -1065,13 +1141,13 @@ + } + } + +- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) { +- if (player.isSpectator()) { ++ private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot ++ if (entityplayer.isSpectator()) { + return false; + } else { +- double d0 = ChunkMap.euclideanDistanceSquared(pos, player); ++ double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer); + +- return d0 < 16384.0D; ++ return d0 < range; // Spigot + } + } + +@@ -1215,9 +1291,19 @@ + } + + public void addEntity(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server ++ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { ++ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); ++ return; ++ } ++ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; ++ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot + + if (i != 0) { + int j = entitytypes.updateInterval(); +@@ -1250,6 +1336,7 @@ + } + + protected void removeEntity(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot + if (entity instanceof ServerPlayer entityplayer) { + this.updatePlayerStatus(entityplayer, false); + ObjectIterator objectiterator = this.entityMap.values().iterator(); +@@ -1391,7 +1478,7 @@ + }); + } + +- private class ChunkDistanceManager extends DistanceManager { ++ public class ChunkDistanceManager extends DistanceManager { // Paper - public + + protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) { + super(workerExecutor, mainThreadExecutor); +@@ -1421,10 +1508,10 @@ + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = Sets.newIdentityHashSet(); ++ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + + public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { +- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast); ++ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit + this.entity = entity; + this.range = i; + this.lastSectionPos = SectionPos.of((EntityAccess) entity); +@@ -1469,6 +1556,7 @@ + } + + public void removePlayer(ServerPlayer player) { ++ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + } +@@ -1476,17 +1564,41 @@ + } + + public void updatePlayer(ServerPlayer player) { ++ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { +- Vec3 vec3d = player.position().subtract(this.entity.position()); ++ // Paper start - remove allocation of Vec3D here ++ // Vec3 vec3d = player.position().subtract(this.entity.position()); ++ double vec3d_dx = player.getX() - this.entity.getX(); ++ double vec3d_dz = player.getZ() - this.entity.getZ(); ++ // Paper end - remove allocation of Vec3D here + int i = ChunkMap.this.getPlayerViewDistance(player); + double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); +- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; ++ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper + double d2 = d0 * d0; +- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper start - Configurable entity tracking range by Y ++ boolean flag = d1 <= d2; ++ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { ++ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); ++ if (rangeY != -1) { ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; ++ } ++ } ++ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper end - Configurable entity tracking range by Y + ++ // CraftBukkit start - respect vanish API ++ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ flag = false; ++ } ++ // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ // Paper start - entity tracking events ++ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); ++ } ++ // Paper end - entity tracking events + } + } else if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); +@@ -1506,6 +1618,7 @@ + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getType().clientTrackingRange() * 16; ++ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + + if (j > i) { + i = j; diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch new file mode 100644 index 0000000000..60e1437870 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch @@ -0,0 +1,152 @@ +--- a/net/minecraft/server/level/DistanceManager.java ++++ b/net/minecraft/server/level/DistanceManager.java +@@ -117,8 +117,17 @@ + + ChunkHolder playerchunk; + ++ // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus + while (iterator.hasNext()) { + playerchunk = (ChunkHolder) iterator.next(); ++ playerchunk.callEventIfUnloading(chunkLoadingManager); ++ } ++ ++ iterator = this.chunksToUpdateFutures.iterator(); ++ // CraftBukkit end ++ ++ while (iterator.hasNext()) { ++ playerchunk = (ChunkHolder) iterator.next(); + playerchunk.updateHighestAllowedStatus(chunkLoadingManager); + } + +@@ -165,30 +174,33 @@ + } + } + +- void addTicket(long position, Ticket ticket) { +- SortedArraySet> arraysetsorted = this.getTickets(position); ++ boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ SortedArraySet> arraysetsorted = this.getTickets(i); + int j = DistanceManager.getTicketLevelAt(arraysetsorted); + Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); + + ticket1.setCreatedTick(this.ticketTickCounter); + if (ticket.getTicketLevel() < j) { +- this.ticketTracker.update(position, ticket.getTicketLevel(), true); ++ this.ticketTracker.update(i, ticket.getTicketLevel(), true); + } + ++ return ticket == ticket1; // CraftBukkit + } + +- void removeTicket(long pos, Ticket ticket) { +- SortedArraySet> arraysetsorted = this.getTickets(pos); ++ boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ SortedArraySet> arraysetsorted = this.getTickets(i); + ++ boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { +- ; ++ removed = true; // CraftBukkit + } + + if (arraysetsorted.isEmpty()) { +- this.tickets.remove(pos); ++ this.tickets.remove(i); + } + +- this.ticketTracker.update(pos, DistanceManager.getTicketLevelAt(arraysetsorted), false); ++ this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); ++ return removed; // CraftBukkit + } + + public void addTicket(TicketType type, ChunkPos pos, int level, T argument) { +@@ -202,19 +214,33 @@ + } + + public void addRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { +- Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument); +- long j = pos.toLong(); ++ // CraftBukkit start ++ this.addRegionTicketAtDistance(type, pos, radius, argument); ++ } + +- this.addTicket(j, ticket); ++ public boolean addRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { ++ // CraftBukkit end ++ Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); ++ long j = chunkcoordintpair.toLong(); ++ ++ boolean added = this.addTicket(j, ticket); // CraftBukkit + this.tickingTicketsTracker.addTicket(j, ticket); ++ return added; // CraftBukkit + } + + public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { +- Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument); +- long j = pos.toLong(); ++ // CraftBukkit start ++ this.removeRegionTicketAtDistance(type, pos, radius, argument); ++ } + +- this.removeTicket(j, ticket); ++ public boolean removeRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { ++ // CraftBukkit end ++ Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); ++ long j = chunkcoordintpair.toLong(); ++ ++ boolean removed = this.removeTicket(j, ticket); // CraftBukkit + this.tickingTicketsTracker.removeTicket(j, ticket); ++ return removed; // CraftBukkit + } + + private SortedArraySet> getTickets(long position) { +@@ -253,9 +279,10 @@ + ChunkPos chunkcoordintpair = pos.chunk(); + long i = chunkcoordintpair.toLong(); + ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); ++ if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(player); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); +@@ -358,7 +385,7 @@ + } + + public void removeTicketsOnClosing() { +- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN); ++ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve + ObjectIterator>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + + while (objectiterator.hasNext()) { +@@ -389,7 +416,27 @@ + + public boolean hasTickets() { + return !this.tickets.isEmpty(); ++ } ++ ++ // CraftBukkit start ++ public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { ++ Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); ++ ++ for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ Entry>> entry = iterator.next(); ++ SortedArraySet> tickets = entry.getValue(); ++ if (tickets.remove(target)) { ++ // copied from removeTicket ++ this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); ++ ++ // can't use entry after it's removed ++ if (tickets.isEmpty()) { ++ iterator.remove(); ++ } ++ } ++ } + } ++ // CraftBukkit end + + private class ChunkTicketTracker extends ChunkTracker { + diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch new file mode 100644 index 0000000000..f7949f17c6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch @@ -0,0 +1,255 @@ +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -74,6 +74,13 @@ + @Nullable + @VisibleForDebug + private NaturalSpawner.SpawnState lastSpawnState; ++ // Paper start ++ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); ++ public int getFullChunksCount() { ++ return this.fullChunks.size(); ++ } ++ long chunkFutureAwaitCounter; ++ // Paper end + + public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { + this.level = world; +@@ -95,6 +102,64 @@ + this.clearCache(); + } + ++ // CraftBukkit start - properly implement isChunkLoaded ++ public boolean isChunkLoaded(int chunkX, int chunkZ) { ++ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ)); ++ if (chunk == null) { ++ return false; ++ } ++ return chunk.getFullChunkNow() != null; ++ } ++ // CraftBukkit end ++ // Paper start ++ public void addLoadedChunk(LevelChunk chunk) { ++ this.fullChunks.put(chunk.coordinateKey, chunk); ++ } ++ ++ public void removeLoadedChunk(LevelChunk chunk) { ++ this.fullChunks.remove(chunk.coordinateKey); ++ } ++ ++ @Nullable ++ public ChunkAccess getChunkAtImmediately(int x, int z) { ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); ++ if (holder == null) { ++ return null; ++ } ++ ++ return holder.getLatestChunk(); ++ } ++ ++ public void addTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { ++ this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier); ++ } ++ ++ public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { ++ this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier); ++ } ++ ++ // "real" get chunk if loaded ++ // Note: Partially copied from the getChunkAt method below ++ @Nullable ++ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) { ++ long k = ChunkPos.asLong(x, z); ++ ++ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe ++ ++ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k); ++ if (playerChunk == null) { ++ return null; ++ } ++ ++ return playerChunk.getFullChunkNowUnchecked(); ++ } ++ ++ @Nullable ++ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { ++ return this.fullChunks.get(ChunkPos.asLong(x, z)); ++ } ++ // Paper end ++ + @Override + public ThreadedLevelLightEngine getLightEngine() { + return this.lightEngine; +@@ -138,7 +203,7 @@ + if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { + ChunkAccess ichunkaccess = this.lastChunk[l]; + +- if (ichunkaccess != null || !create) { ++ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime + return ichunkaccess; + } + } +@@ -150,8 +215,9 @@ + + Objects.requireNonNull(completablefuture); + chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads + ChunkResult chunkresult = (ChunkResult) completablefuture.join(); +- ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse((Object) null); ++ ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error + + if (ichunkaccess1 == null && create) { + throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError())); +@@ -231,7 +297,15 @@ + int l = ChunkLevel.byStatus(leastStatus); + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); + +- if (create) { ++ // CraftBukkit start - don't add new ticket for currently unloading chunk ++ boolean currentlyUnloading = false; ++ if (playerchunk != null) { ++ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); ++ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); ++ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); ++ } ++ if (create && !currentlyUnloading) { ++ // CraftBukkit end + this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); + if (this.chunkAbsent(playerchunk, l)) { + ProfilerFiller gameprofilerfiller = Profiler.get(); +@@ -250,7 +324,7 @@ + } + + private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { +- return holder == null || holder.getTicketLevel() > maxLevel; ++ return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks + } + + @Override +@@ -279,7 +353,7 @@ + return this.mainThreadProcessor.pollTask(); + } + +- boolean runDistanceManagerUpdates() { ++ public boolean runDistanceManagerUpdates() { // Paper - public + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = this.chunkMap.promoteChunkMap(); + +@@ -309,18 +383,40 @@ + + @Override + public void close() throws IOException { +- this.save(true); ++ // CraftBukkit start ++ this.close(true); ++ } ++ ++ public void close(boolean save) throws IOException { ++ if (save) { ++ this.save(true); ++ } ++ // CraftBukkit end + this.dataStorage.close(); + this.lightEngine.close(); + this.chunkMap.close(); + } + ++ // CraftBukkit start - modelled on below ++ public void purgeUnload() { ++ ProfilerFiller gameprofilerfiller = Profiler.get(); ++ ++ gameprofilerfiller.push("purge"); ++ this.distanceManager.purgeStaleTickets(); ++ this.runDistanceManagerUpdates(); ++ gameprofilerfiller.popPush("unload"); ++ this.chunkMap.tick(() -> true); ++ gameprofilerfiller.pop(); ++ this.clearCache(); ++ } ++ // CraftBukkit end ++ + @Override + public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("purge"); +- if (this.level.tickRateManager().runsNormally() || !tickChunks) { ++ if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot + this.distanceManager.purgeStaleTickets(); + } + +@@ -401,14 +497,22 @@ + + this.lastSpawnState = spawnercreature_d; + profiler.popPush("spawnAndTick"); +- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING); ++ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + List list1; + + if (flag && (this.spawnEnemies || this.spawnFriendlies)) { +- boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L; ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ for (ServerPlayer entityPlayer : this.level.players()) { ++ int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance()); ++ chunkRange = Math.min(chunkRange, 8); ++ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); ++ entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent ++ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit + +- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); ++ list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit + } else { + list1 = List.of(); + } +@@ -420,7 +524,7 @@ + ChunkPos chunkcoordintpair = chunk.getPos(); + + chunk.incrementInhabitedTime(timeDelta); +- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { ++ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot + NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1); + } + +@@ -541,10 +645,16 @@ + + @Override + public void setSpawnSettings(boolean spawnMonsters) { +- this.spawnEnemies = spawnMonsters; +- this.spawnFriendlies = this.spawnFriendlies; ++ // CraftBukkit start ++ this.setSpawnSettings(spawnMonsters, this.spawnFriendlies); + } + ++ public void setSpawnSettings(boolean flag, boolean spawnFriendlies) { ++ this.spawnEnemies = flag; ++ this.spawnFriendlies = spawnFriendlies; ++ // CraftBukkit end ++ } ++ + public String getChunkDebugData(ChunkPos pos) { + return this.chunkMap.getChunkDebugData(pos); + } +@@ -618,14 +728,20 @@ + } + + @Override +- protected boolean pollTask() { ++ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task ++ public boolean pollTask() { ++ try { + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; + } else { + ServerChunkCache.this.lightEngine.tryScheduleUpdate(); + return super.pollTask(); + } ++ } finally { ++ ServerChunkCache.this.chunkMap.callbackExecutor.run(); + } ++ // CraftBukkit end ++ } + } + + private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) { diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch new file mode 100644 index 0000000000..f458422c50 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch @@ -0,0 +1,179 @@ +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -31,7 +31,6 @@ + import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket; + import net.minecraft.network.protocol.game.VecDeltaCodec; + import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EquipmentSlot; + import net.minecraft.world.entity.Leashable; +@@ -50,6 +49,13 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import net.minecraft.server.network.ServerPlayerConnection; ++import net.minecraft.util.Mth; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerVelocityEvent; ++// CraftBukkit end ++ + public class ServerEntity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -69,18 +75,22 @@ + private Vec3 lastSentMovement; + private int tickCount; + private int teleportDelay; +- private List lastPassengers = Collections.emptyList(); ++ private List lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks + private boolean wasRiding; + private boolean wasOnGround; + @Nullable + private List> trackedDataValues; ++ // CraftBukkit start ++ private final Set trackedPlayers; + +- public ServerEntity(ServerLevel world, Entity entity, int tickInterval, boolean alwaysUpdateVelocity, Consumer> receiver) { +- this.level = world; +- this.broadcast = receiver; ++ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { ++ this.trackedPlayers = trackedPlayers; ++ // CraftBukkit end ++ this.level = worldserver; ++ this.broadcast = consumer; + this.entity = entity; +- this.updateInterval = tickInterval; +- this.trackDelta = alwaysUpdateVelocity; ++ this.updateInterval = i; ++ this.trackDelta = flag; + this.positionCodec.setBase(entity.trackingPosition()); + this.lastSentMovement = entity.getDeltaMovement(); + this.lastSentYRot = Mth.packDegrees(entity.getYRot()); +@@ -94,7 +104,7 @@ + List list = this.entity.getPassengers(); + + if (!list.equals(this.lastPassengers)) { +- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity)); ++ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit + ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> { + if (entity instanceof ServerPlayer entityplayer) { + entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot()); +@@ -106,19 +116,19 @@ + + Entity entity = this.entity; + +- if (entity instanceof ItemFrame entityitemframe) { +- if (this.tickCount % 10 == 0) { ++ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it ++ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block + ItemStack itemstack = entityitemframe.getItem(); + +- if (itemstack.getItem() instanceof MapItem) { +- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); ++ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable ++ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); + + if (worldmap != null) { +- Iterator iterator = this.level.players().iterator(); ++ Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit + + while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); ++ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit + + worldmap.tickCarriedBy(entityplayer, itemstack); + Packet packet = worldmap.getUpdatePacket(mapid, entityplayer); +@@ -168,7 +178,13 @@ + + ++this.teleportDelay; + Vec3 vec3d = this.entity.trackingPosition(); +- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; ++ // Paper start - reduce allocation of Vec3D here ++ Vec3 base = this.positionCodec.base; ++ double vec3d_dx = vec3d.x - base.x; ++ double vec3d_dy = vec3d.y - base.y; ++ double vec3d_dz = vec3d.z - base.z; ++ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; ++ // Paper end - reduce allocation of Vec3D here + Packet packet1 = null; + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = false; +@@ -248,6 +264,27 @@ + + ++this.tickCount; + if (this.entity.hurtMarked) { ++ // CraftBukkit start - Create PlayerVelocity event ++ boolean cancelled = false; ++ ++ if (this.entity instanceof ServerPlayer) { ++ Player player = (Player) this.entity.getBukkitEntity(); ++ org.bukkit.util.Vector velocity = player.getVelocity(); ++ ++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone()); ++ this.entity.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ cancelled = true; ++ } else if (!velocity.equals(event.getVelocity())) { ++ player.setVelocity(event.getVelocity()); ++ } ++ } ++ ++ if (cancelled) { ++ return; ++ } ++ // CraftBukkit end + this.entity.hurtMarked = false; + this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); + } +@@ -298,7 +335,10 @@ + + public void sendPairingData(ServerPlayer player, Consumer> sender) { + if (this.entity.isRemoved()) { +- ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity); ++ // CraftBukkit start - Remove useless error spam, just return ++ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity); ++ return; ++ // CraftBukkit end + } + + Packet packet = this.entity.getAddEntityPacket(this); +@@ -313,6 +353,12 @@ + if (this.entity instanceof LivingEntity) { + Collection collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes(); + ++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health ++ if (this.entity.getId() == player.getId()) { ++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false); ++ } ++ // CraftBukkit end ++ + if (!collection.isEmpty()) { + sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); + } +@@ -342,8 +388,9 @@ + } + + if (!list.isEmpty()) { +- sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); ++ sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization + } ++ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending + } + + if (!this.entity.getPassengers().isEmpty()) { +@@ -396,6 +443,11 @@ + Set set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync(); + + if (!set.isEmpty()) { ++ // CraftBukkit start - Send scaled max health ++ if (this.entity instanceof ServerPlayer) { ++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false); ++ } ++ // CraftBukkit end + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set)); + } + diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch new file mode 100644 index 0000000000..d9915e3d7d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -0,0 +1,1261 @@ +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -58,7 +58,6 @@ + import net.minecraft.network.protocol.game.ClientboundDamageEventPacket; + import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; + import net.minecraft.network.protocol.game.ClientboundExplodePacket; +-import net.minecraft.network.protocol.game.ClientboundGameEventPacket; + import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; + import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; + import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; +@@ -124,6 +123,7 @@ + import net.minecraft.world.level.StructureManager; + import net.minecraft.world.level.WorldGenLevel; + import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.biome.BiomeSource; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.SnowLayerBlock; +@@ -149,7 +149,9 @@ + import net.minecraft.world.level.gameevent.DynamicGameEventListener; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.gameevent.GameEventDispatcher; ++import net.minecraft.world.level.levelgen.FlatLevelSource; + import net.minecraft.world.level.levelgen.Heightmap; ++import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; + import net.minecraft.world.level.levelgen.structure.BoundingBox; + import net.minecraft.world.level.levelgen.structure.Structure; + import net.minecraft.world.level.levelgen.structure.StructureCheck; +@@ -165,7 +167,7 @@ + import net.minecraft.world.level.saveddata.maps.MapItemSavedData; + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; +-import net.minecraft.world.level.storage.ServerLevelData; ++import net.minecraft.world.level.storage.PrimaryLevelData; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.BooleanOp; +@@ -173,6 +175,16 @@ + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.ticks.LevelTicks; + import org.slf4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.WeatherType; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.generator.CustomWorldChunkManager; ++import org.bukkit.craftbukkit.util.WorldUUID; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.server.MapInitializeEvent; ++import org.bukkit.event.weather.LightningStrikeEvent; ++import org.bukkit.event.world.TimeSkipEvent; ++// CraftBukkit end + + public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel { + +@@ -187,7 +199,7 @@ + final List players = Lists.newArrayList(); + public final ServerChunkCache chunkSource; + private final MinecraftServer server; +- public final ServerLevelData serverLevelData; ++ public final PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; + final EntityTickList entityTickList = new EntityTickList(); + public final PersistentEntitySectionManager entityManager; +@@ -214,54 +226,204 @@ + private final boolean tickTime; + private final RandomSequences randomSequences; + +- public ServerLevel(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey worldKey, LevelStem dimensionOptions, ChunkProgressListener worldGenerationProgressListener, boolean debugWorld, long seed, List spawners, boolean shouldTickTime, @Nullable RandomSequences randomSequencesState) { +- super(properties, worldKey, server.registryAccess(), dimensionOptions.type(), false, debugWorld, seed, server.getMaxChainedNeighborUpdates()); +- this.tickTime = shouldTickTime; +- this.server = server; +- this.customSpawners = spawners; +- this.serverLevelData = properties; +- ChunkGenerator chunkgenerator = dimensionOptions.generator(); +- boolean flag2 = server.forceSynchronousWrites(); +- DataFixer datafixer = server.getFixerUpper(); +- EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(session.getLevelId(), worldKey, "entities"), session.getDimensionPath(worldKey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, server); ++ // CraftBukkit start ++ public final LevelStorageSource.LevelStorageAccess convertable; ++ public final UUID uuid; ++ public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent ++ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent ++ ++ public LevelChunk getChunkIfLoaded(int x, int z) { ++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately ++ } ++ ++ @Override ++ public ResourceKey getTypeKey() { ++ return this.convertable.dimensionType; ++ } ++ ++ // Paper start ++ public final boolean areChunksLoadedForMove(AABB axisalignedbb) { ++ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override ++ // ICollisionAccess methods for VoxelShapes) ++ // be more strict too, add a block (dumb plugins in move events?) ++ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ ++ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ ServerChunkCache chunkProvider = this.getChunkSource(); ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ if (Thread.currentThread() != this.thread) { ++ this.getChunkSource().mainThreadProcessor.execute(() -> { ++ this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); ++ }); ++ return; ++ } ++ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ ++ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int minChunkZ = minBlockZ >> 4; ++ ++ int maxChunkX = maxBlockX >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ this.loadChunks(minChunkX, minChunkZ, maxChunkX, maxChunkZ, priority, onLoad); ++ } ++ ++ public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ, ++ ca.spottedleaf.concurrentutil.util.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ List ret = new java.util.ArrayList<>(); ++ it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); ++ ServerChunkCache chunkProvider = this.getChunkSource(); + ++ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); ++ int[] loadedChunks = new int[1]; ++ ++ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); ++ ++ java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { ++ if (chunk != null) { ++ int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); ++ ret.add(chunk); ++ ticketLevels.add(ticketLevel); ++ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); ++ } ++ if (++loadedChunks[0] == requiredChunks) { ++ try { ++ onLoad.accept(java.util.Collections.unmodifiableList(ret)); ++ } finally { ++ for (int i = 0, len = ret.size(); i < len; ++i) { ++ ChunkPos chunkPos = ret.get(i).getPos(); ++ int ticketLevel = ticketLevels.getInt(i); ++ ++ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); ++ } ++ } ++ } ++ }; ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad( ++ this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer ++ ); ++ } ++ } ++ } ++ // Paper end ++ ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ final Player player = this.getServer().getPlayerList().getPlayer(uuid); ++ return player != null && player.level() == this ? player : null; ++ } ++ // Paper end - optimise getPlayerByUUID ++ ++ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer ++ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { ++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs ++ this.pvpMode = minecraftserver.isPvpAllowed(); ++ this.convertable = convertable_conversionsession; ++ this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); ++ // CraftBukkit end ++ this.tickTime = flag1; ++ this.server = minecraftserver; ++ this.customSpawners = list; ++ this.serverLevelData = iworlddataserver; ++ ChunkGenerator chunkgenerator = worlddimension.generator(); ++ // CraftBukkit start ++ this.serverLevelData.setWorld(this); ++ ++ if (biomeProvider != null) { ++ BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider ++ if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) { ++ chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings); ++ } else if (chunkgenerator instanceof FlatLevelSource cpf) { ++ chunkgenerator = new FlatLevelSource(cpf.settings(), worldChunkManager); ++ } ++ } ++ ++ if (gen != null) { ++ chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen); ++ } ++ // CraftBukkit end ++ boolean flag2 = minecraftserver.forceSynchronousWrites(); ++ DataFixer datafixer = minecraftserver.getFixerUpper(); ++ EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver); ++ + this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); +- StructureTemplateManager structuretemplatemanager = server.getStructureManager(); +- int j = server.getPlayerList().getViewDistance(); +- int k = server.getPlayerList().getSimulationDistance(); ++ StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); ++ int j = this.spigotConfig.viewDistance; // Spigot ++ int k = this.spigotConfig.simulationDistance; // Spigot + PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; + + Objects.requireNonNull(this.entityManager); +- this.chunkSource = new ServerChunkCache(this, session, datafixer, structuretemplatemanager, workerExecutor, chunkgenerator, j, k, flag2, worldGenerationProgressListener, persistententitysectionmanager::updateChunkStatus, () -> { +- return server.overworld().getDataStorage(); ++ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> { ++ return minecraftserver.overworld().getDataStorage(); + }); + this.chunkSource.getGeneratorState().ensureStructuresGenerated(); + this.portalForcer = new PortalForcer(this); + this.updateSkyBrightness(); + this.prepareWeather(); +- this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize()); ++ this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize()); + this.raids = (Raids) this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration())); +- if (!server.isSingleplayer()) { +- properties.setGameType(server.getDefaultGameType()); ++ if (!minecraftserver.isSingleplayer()) { ++ iworlddataserver.setGameType(minecraftserver.getDefaultGameType()); + } + +- long l = server.getWorldData().worldGenOptions().seed(); ++ long l = minecraftserver.getWorldData().worldGenOptions().seed(); + +- this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), server.getStructureManager(), worldKey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); +- this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck); +- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) { +- this.dragonFight = new EndDragonFight(this, l, server.getWorldData().endDragonFightData()); ++ this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), this.getTypeKey(), chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); // Paper - Fix missing CB diff ++ this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), this.structureCheck); // CraftBukkit ++ if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END ++ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit + } else { + this.dragonFight = null; + } + + this.sleepStatus = new SleepStatus(); + this.gameEventDispatcher = new GameEventDispatcher(this); +- this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomSequencesState, () -> { ++ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> { + return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences"); + }); ++ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + } + ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; ++ } ++ // Paper end ++ + /** @deprecated */ + @Deprecated + @VisibleForTesting +@@ -273,8 +435,8 @@ + this.serverLevelData.setClearWeatherTime(clearDuration); + this.serverLevelData.setRainTime(rainDuration); + this.serverLevelData.setThunderTime(rainDuration); +- this.serverLevelData.setRaining(raining); +- this.serverLevelData.setThundering(thundering); ++ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents + } + + @Override +@@ -305,12 +467,20 @@ + long j; + + if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { ++ // CraftBukkit start ++ j = this.levelData.getDayTime() + 24000L; ++ TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime()); + if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { +- j = this.levelData.getDayTime() + 24000L; +- this.setDayTime(j - j % 24000L); ++ this.getCraftServer().getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.setDayTime(this.getDayTime() + event.getSkipAmount()); ++ } + } + +- this.wakeUpAllPlayers(); ++ if (!event.isCancelled()) { ++ this.wakeUpAllPlayers(); ++ } ++ // CraftBukkit end + if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { + this.resetWeatherCycle(); + } +@@ -325,9 +495,9 @@ + if (!this.isDebug() && flag) { + j = this.getGameTime(); + gameprofilerfiller.push("blockTicks"); +- this.blockTicks.tick(j, 65536, this::tickBlock); ++ this.blockTicks.tick(j, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks + gameprofilerfiller.popPush("fluidTicks"); +- this.fluidTicks.tick(j, 65536, this::tickFluid); ++ this.fluidTicks.tick(j, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks + gameprofilerfiller.pop(); + } + +@@ -345,7 +515,7 @@ + + this.handlingTick = false; + gameprofilerfiller.pop(); +- boolean flag1 = !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); ++ boolean flag1 = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this + + if (flag1) { + this.resetEmptyTime(); +@@ -359,6 +529,7 @@ + gameprofilerfiller.pop(); + } + ++ org.spigotmc.ActivationRange.activateEntities(this); // Spigot + this.entityTickList.forEach((entity) -> { + if (!entity.isRemoved()) { + if (!tickratemanager.isEntityFrozen(entity)) { +@@ -429,7 +600,7 @@ + + private void wakeUpAllPlayers() { + this.sleepStatus.removeAllSleepers(); +- ((List) this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { ++ (this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { // CraftBukkit - decompile error + entityplayer.stopSleepInBed(false, false); + }); + } +@@ -442,12 +613,12 @@ + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("thunder"); +- if (flag && this.isThundering() && this.random.nextInt(100000) == 0) { ++ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder + BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); +- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); ++ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses + + if (flag1) { + SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); +@@ -456,7 +627,7 @@ + entityhorseskeleton.setTrap(true); + entityhorseskeleton.setAge(0); + entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); +- this.addFreshEntity(entityhorseskeleton); ++ this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit + } + } + +@@ -465,18 +636,20 @@ + if (entitylightning != null) { + entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition)); + entitylightning.setVisualOnly(flag1); +- this.addFreshEntity(entitylightning); ++ this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit + } + } + } + + gameprofilerfiller.popPush("iceandsnow"); + ++ if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int l = 0; l < randomTickSpeed; ++l) { + if (this.random.nextInt(48) == 0) { + this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); + } + } ++ } // Paper - Option to disable ice and snow + + gameprofilerfiller.popPush("tickBlocks"); + if (randomTickSpeed > 0) { +@@ -521,7 +694,7 @@ + Biome biomebase = (Biome) this.getBiome(blockposition1).value(); + + if (biomebase.shouldFreeze(this, blockposition2)) { +- this.setBlockAndUpdate(blockposition2, Blocks.ICE.defaultBlockState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit + } + + if (this.isRaining()) { +@@ -537,10 +710,10 @@ + BlockState iblockdata1 = (BlockState) iblockdata.setValue(SnowLayerBlock.LAYERS, j + 1); + + Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1); +- this.setBlockAndUpdate(blockposition1, iblockdata1); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit + } + } else { +- this.setBlockAndUpdate(blockposition1, Blocks.SNOW.defaultBlockState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit + } + } + +@@ -568,6 +741,11 @@ + } + + protected BlockPos findLightningTargetAround(BlockPos pos) { ++ // Paper start - Add methods to find targets for lightning strikes ++ return this.findLightningTargetAround(pos, false); ++ } ++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { ++ // Paper end - Add methods to find targets for lightning strikes + BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); + Optional optional = this.findLightningRod(blockposition1); + +@@ -576,12 +754,13 @@ + } else { + AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, blockposition1.atY(this.getMaxY() + 1)).inflate(3.0D); + List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { +- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); ++ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) + }); + + if (!list.isEmpty()) { + return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); + } else { ++ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes + if (blockposition1.getY() == this.getMinY() - 1) { + blockposition1 = blockposition1.above(2); + } +@@ -679,8 +858,8 @@ + this.serverLevelData.setThunderTime(j); + this.serverLevelData.setRainTime(k); + this.serverLevelData.setClearWeatherTime(i); +- this.serverLevelData.setThundering(flag1); +- this.serverLevelData.setRaining(flag2); ++ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents + } + + this.oThunderLevel = this.thunderLevel; +@@ -701,33 +880,67 @@ + this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F); + } + ++ /* CraftBukkit start + if (this.oRainLevel != this.rainLevel) { +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension()); ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension()); + } + + if (this.oThunderLevel != this.thunderLevel) { +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension()); ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension()); + } + + if (flag != this.isRaining()) { + if (flag) { +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0.0F)); +- } else { +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F)); ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.STOP_RAINING, 0.0F)); ++ } else { ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); + } + +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel)); +- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel)); ++ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel)); + } ++ // */ ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).tickWeather(); ++ } ++ } + ++ if (flag != this.isRaining()) { ++ // Only send weather packets to those affected ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false); ++ } ++ } ++ } ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); ++ } ++ } ++ // CraftBukkit end ++ + } + + @VisibleForTesting + public void resetWeatherCycle() { +- this.serverLevelData.setRainTime(0); +- this.serverLevelData.setRaining(false); +- this.serverLevelData.setThunderTime(0); +- this.serverLevelData.setThundering(false); ++ // CraftBukkit start ++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents ++ // 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 (!this.serverLevelData.isRaining()) { ++ this.serverLevelData.setRainTime(0); ++ } ++ // CraftBukkit end ++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents ++ // 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 (!this.serverLevelData.isThundering()) { ++ this.serverLevelData.setThunderTime(0); ++ } ++ // CraftBukkit end + } + + public void resetEmptyTime() { +@@ -754,6 +967,13 @@ + } + + public void tickNonPassenger(Entity entity) { ++ // Spigot start ++ if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { ++ entity.tickCount++; ++ entity.inactiveTick(); ++ return; ++ } ++ // Spigot end + entity.setOldPosAndRot(); + ProfilerFiller gameprofilerfiller = Profiler.get(); + +@@ -763,6 +983,7 @@ + }); + gameprofilerfiller.incrementCounter("tickNonPassenger"); + entity.tick(); ++ entity.postTick(); // CraftBukkit + gameprofilerfiller.pop(); + Iterator iterator = entity.getPassengers().iterator(); + +@@ -786,6 +1007,7 @@ + }); + gameprofilerfiller.incrementCounter("tickPassenger"); + passenger.rideTick(); ++ passenger.postTick(); // CraftBukkit + gameprofilerfiller.pop(); + Iterator iterator = passenger.getPassengers().iterator(); + +@@ -810,6 +1032,7 @@ + ServerChunkCache chunkproviderserver = this.getChunkSource(); + + if (!savingDisabled) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit + if (progressListener != null) { + progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); + } +@@ -827,11 +1050,19 @@ + } + + } ++ ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ ServerLevel worldserver1 = this; ++ ++ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); ++ this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); ++ // CraftBukkit end + } + + private void saveLevelData(boolean flush) { + if (this.dragonFight != null) { +- this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData()); ++ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit + } + + DimensionDataStorage worldpersistentdata = this.getChunkSource().getDataStorage(); +@@ -903,18 +1134,40 @@ + + @Override + public boolean addFreshEntity(Entity entity) { +- return this.addEntity(entity); ++ // CraftBukkit start ++ return this.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.DEFAULT); + } + ++ @Override ++ public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) { ++ return this.addEntity(entity, reason); ++ // CraftBukkit end ++ } ++ + public boolean addWithUUID(Entity entity) { +- return this.addEntity(entity); ++ // CraftBukkit start ++ return this.addWithUUID(entity, CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public boolean addWithUUID(Entity entity, CreatureSpawnEvent.SpawnReason reason) { ++ return this.addEntity(entity, reason); ++ // CraftBukkit end + } + + public void addDuringTeleport(Entity entity) { ++ // CraftBukkit start ++ // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds, ++ // since it is only an implementation detail, that a new entity is created when ++ // they are traveling between worlds. ++ this.addDuringTeleport(entity, null); ++ } ++ ++ public void addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) { ++ // CraftBukkit end + if (entity instanceof ServerPlayer entityplayer) { + this.addPlayer(entityplayer); + } else { +- this.addEntity(entity); ++ this.addEntity(entity, reason); // CraftBukkit + } + + } +@@ -939,41 +1192,116 @@ + this.entityManager.addNewEntity(player); + } + +- private boolean addEntity(Entity entity) { ++ // CraftBukkit start ++ private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { ++ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process ++ // Paper start - extra debug info ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); ++ return true; ++ } ++ // Paper end - extra debug info ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason + if (entity.isRemoved()) { +- ServerLevel.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType())); ++ // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ return true; ++ } ++ // Paper end - capture all item additions to the world ++ // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. ++ if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { ++ return false; ++ } ++ // CraftBukkit end ++ + return this.entityManager.addNewEntity(entity); + } + } + + public boolean tryAddFreshEntityWithPassengers(Entity entity) { +- Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); ++ // CraftBukkit start ++ return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ // CraftBukkit end ++ Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error + PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; + + Objects.requireNonNull(this.entityManager); + if (stream.anyMatch(persistententitysectionmanager::isLoaded)) { + return false; + } else { +- this.addFreshEntityWithPassengers(entity); ++ this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit + return true; + } + } + + public void unload(LevelChunk chunk) { ++ // Spigot Start ++ for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { ++ if (tileentity instanceof net.minecraft.world.Container) { ++ // Paper start - this area looks like it can load chunks, change the behavior ++ // chests for example can apply physics to the world ++ // so instead we just change the active container and call the event ++ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { ++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ } ++ // Paper end - this area looks like it can load chunks, change the behavior ++ } ++ } ++ // Spigot End + chunk.clearAllBlockEntities(); + chunk.unregisterTickContainerFromLevel(this); + } + + public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) { +- player.remove(reason); ++ player.remove(reason, null); // CraftBukkit - add Bukkit remove cause + } + ++ // CraftBukkit start ++ public boolean strikeLightning(Entity entitylightning) { ++ return this.strikeLightning(entitylightning, LightningStrikeEvent.Cause.UNKNOWN); ++ } ++ ++ public boolean strikeLightning(Entity entitylightning, LightningStrikeEvent.Cause cause) { ++ LightningStrikeEvent lightning = CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause); ++ ++ if (lightning.isCancelled()) { ++ return false; ++ } ++ ++ return this.addFreshEntity(entitylightning); ++ } ++ // CraftBukkit end ++ + @Override + public void destroyBlockProgress(int entityId, BlockPos pos, int progress) { + Iterator iterator = this.server.getPlayerList().getPlayers().iterator(); + ++ // CraftBukkit start ++ Player entityhuman = null; ++ Entity entity = this.getEntity(entityId); ++ if (entity instanceof Player) entityhuman = (Player) entity; ++ // CraftBukkit end ++ ++ // Paper start - Add BlockBreakProgressUpdateEvent ++ // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server. ++ // Hence, do not call the event. ++ if (entity != null) { ++ float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f; ++ org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos); ++ new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity()) ++ .callEvent(); ++ } ++ // Paper end - Add BlockBreakProgressUpdateEvent ++ + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +@@ -982,6 +1310,12 @@ + double d1 = (double) pos.getY() - entityplayer.getY(); + double d2 = (double) pos.getZ() - entityplayer.getZ(); + ++ // CraftBukkit start ++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) { ++ continue; ++ } ++ // CraftBukkit end ++ + if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) { + entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress)); + } +@@ -1030,7 +1364,7 @@ + + @Override + public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) { +- this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false)); ++ this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false)); // Paper - diff on change (the 64.0 distance is used as defaults for sound ranges in spigot config for ender dragon, end portal and wither) + } + + public int getLogicalHeight() { +@@ -1039,6 +1373,11 @@ + + @Override + public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { ++ // Paper start - Prevent GameEvents being fired from unloaded chunks ++ if (this.getChunkIfLoadedImmediately((Mth.floor(emitterPos.x) >> 4), (Mth.floor(emitterPos.z) >> 4)) == null) { ++ return; ++ } ++ // Paper end - Prevent GameEvents being fired from unloaded chunks + this.gameEventDispatcher.post(event, emitterPos, emitter); + } + +@@ -1052,6 +1391,7 @@ + + this.getChunkSource().blockChanged(pos); + this.pathTypesByPosCache.invalidate(pos); ++ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape voxelshape = oldState.getCollisionShape(this, pos); + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + +@@ -1060,7 +1400,18 @@ + Iterator iterator = this.navigatingMobs.iterator(); + + while (iterator.hasNext()) { +- Mob entityinsentient = (Mob) iterator.next(); ++ // CraftBukkit start - fix SPIGOT-6362 ++ Mob entityinsentient; ++ try { ++ entityinsentient = (Mob) iterator.next(); ++ } catch (java.util.ConcurrentModificationException ex) { ++ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register ++ // In this case we just run the update again across all the iterators as the chunk will then be loaded ++ // As this is a relative edge case it is much faster than copying navigators (on either read or write) ++ this.sendBlockUpdated(pos, oldState, newState, flags); ++ return; ++ } ++ // CraftBukkit end + PathNavigation navigationabstract = entityinsentient.getNavigation(); + + if (navigationabstract.shouldRecomputePath(pos)) { +@@ -1082,15 +1433,18 @@ + } + + } ++ } // Paper - option to disable pathfinding updates + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block block) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, (Direction) null, (Direction) null)); + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null, orientation); + } + +@@ -1126,9 +1480,20 @@ + + @Override + public void explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions smallParticle, ParticleOptions largeParticle, Holder soundEvent) { ++ // CraftBukkit start ++ this.explode0(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, smallParticle, largeParticle, soundEvent); ++ } ++ ++ public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder) { ++ // Paper start - Allow explosions to damage source ++ return this.explode0(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, particleparam, particleparam1, holder, null); ++ } ++ public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder, java.util.function.Consumer configurator) { ++ // Paper end - Allow explosions to damage source ++ // CraftBukkit end + Explosion.BlockInteraction explosion_effect; + +- switch (explosionSourceType) { ++ switch (world_a) { + case NONE: + explosion_effect = Explosion.BlockInteraction.KEEP; + break; +@@ -1144,16 +1509,27 @@ + case TRIGGER: + explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK; + break; ++ // CraftBukkit start - handle custom explosion type ++ case STANDARD: ++ explosion_effect = Explosion.BlockInteraction.DESTROY; ++ break; ++ // CraftBukkit end + default: + throw new MatchException((String) null, (Throwable) null); + } + + Explosion.BlockInteraction explosion_effect1 = explosion_effect; +- Vec3 vec3d = new Vec3(x, y, z); +- ServerExplosion serverexplosion = new ServerExplosion(this, entity, damageSource, behavior, vec3d, power, createFire, explosion_effect1); ++ Vec3 vec3d = new Vec3(d0, d1, d2); ++ ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1); ++ if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source + + serverexplosion.explode(); +- ParticleOptions particleparam2 = serverexplosion.isSmall() ? smallParticle : largeParticle; ++ // CraftBukkit start ++ if (serverexplosion.wasCanceled) { ++ return serverexplosion; ++ } ++ // CraftBukkit end ++ ParticleOptions particleparam2 = serverexplosion.isSmall() ? particleparam : particleparam1; + Iterator iterator = this.players.iterator(); + + while (iterator.hasNext()) { +@@ -1162,10 +1538,11 @@ + if (entityplayer.distanceToSqr(vec3d) < 4096.0D) { + Optional optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer)); + +- entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, soundEvent)); ++ entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, holder)); + } + } + ++ return serverexplosion; // CraftBukkit + } + + private Explosion.BlockInteraction getDestroyType(GameRules.Key decayRule) { +@@ -1226,17 +1603,29 @@ + } + + public int sendParticles(T parameters, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) { +- return this.sendParticles(parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed); ++ return this.sendParticlesSource(null, parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support + } + + public int sendParticles(T parameters, boolean force, boolean important, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) { +- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(parameters, force, important, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) speed, count); +- int j = 0; ++ return this.sendParticlesSource(null, parameters, force, important, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support ++ } + +- for (int k = 0; k < this.players.size(); ++k) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); ++ // CraftBukkit start - visibility api support ++ public int sendParticlesSource(ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) { ++ // Paper start - Particle API ++ return this.sendParticlesSource(this.players, sender, t0, flag, flag1, d0, d1, d2, i, d3, d4, d5, d6); ++ } ++ public int sendParticlesSource(List receivers, @Nullable ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) { ++ // Paper end - Particle API ++ // CraftBukkit end ++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, flag, flag1, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); ++ int j = 0; + +- if (this.sendParticles(entityplayer, force, x, y, z, packetplayoutworldparticles)) { ++ for (Player entityhuman : receivers) { // Paper - Particle API ++ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API ++ if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit ++ ++ if (this.sendParticles(entityplayer, flag, d0, d1, d2, packetplayoutworldparticles)) { + ++j; + } + } +@@ -1292,7 +1681,7 @@ + + @Nullable + public BlockPos findNearestMapStructure(TagKey structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) { +- if (!this.server.getWorldData().worldGenOptions().generateStructures()) { ++ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit + return null; + } else { + Optional> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag); +@@ -1334,11 +1723,38 @@ + @Nullable + @Override + public MapItemSavedData getMapData(MapId id) { +- return (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key()); ++ // Paper start - Call missing map initialize event and set id ++ final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); ++ ++ final Optional cacheEntry = storage.cache.get(id.key()); ++ if (cacheEntry == null) { // Cache did not contain, try to load and may init ++ final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), id.key()); // get populates the cache ++ if (worldmap != null) { // map was read, init it and return ++ worldmap.id = id; ++ new MapInitializeEvent(worldmap.mapView).callEvent(); ++ return worldmap; ++ } ++ ++ return null; // Map does not exist, reading failed. ++ } ++ ++ // Cache entry exists, update it with the id ref and return. ++ if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) { ++ mapItemSavedData.id = id; ++ return mapItemSavedData; ++ } ++ ++ return null; ++ // Paper end - Call missing map initialize event and set id + } + + @Override + public void setMapData(MapId id, MapItemSavedData state) { ++ // CraftBukkit start ++ state.id = id; ++ MapInitializeEvent event = new MapInitializeEvent(state.mapView); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ // CraftBukkit end + this.getServer().overworld().getDataStorage().set(id.key(), state); + } + +@@ -1352,18 +1768,28 @@ + float f1 = this.levelData.getSpawnAngle(); + + if (!blockposition1.equals(pos) || f1 != angle) { ++ org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent + this.levelData.setSpawn(pos, angle); ++ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent + this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); + } + + if (this.lastSpawnChunkRadius > 1) { +- this.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(blockposition1), this.lastSpawnChunkRadius, Unit.INSTANCE); ++ // Paper start - allow disabling gamerule limits ++ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(blockposition1, this.lastSpawnChunkRadius - 2)) { ++ this.getChunkSource().removeTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); ++ } ++ // Paper end - allow disabling gamerule limits + } + + int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1; + + if (i > 1) { +- this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), i, Unit.INSTANCE); ++ // Paper start - allow disabling gamerule limits ++ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(pos, i - 2)) { ++ this.getChunkSource().addTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); ++ } ++ // Paper end - allow disabling gamerule limits + } + + this.lastSpawnChunkRadius = i; +@@ -1419,6 +1845,11 @@ + }); + optional1.ifPresent((holder) -> { + this.getServer().execute(() -> { ++ // Paper start - Remove stale POIs ++ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { ++ this.getPoiManager().remove(blockposition1); ++ } ++ // Paper end - Remove stale POIs + this.getPoiManager().add(blockposition1, holder); + DebugPackets.sendPoiAddedPacket(this, blockposition1); + }); +@@ -1649,6 +2080,11 @@ + @Override + public void blockUpdated(BlockPos pos, Block block) { + if (!this.isDebug()) { ++ // CraftBukkit start ++ if (this.populating) { ++ return; ++ } ++ // CraftBukkit end + this.updateNeighborsAt(pos, block); + } + +@@ -1668,12 +2104,12 @@ + } + + public boolean isFlat() { +- return this.server.getWorldData().isFlatWorld(); ++ return this.serverLevelData.isFlatWorld(); // CraftBukkit + } + + @Override + public long getSeed() { +- return this.server.getWorldData().worldGenOptions().seed(); ++ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit + } + + @Nullable +@@ -1696,7 +2132,7 @@ + private static String getTypeCount(Iterable items, Function classifier) { + try { + Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); +- Iterator iterator = items.iterator(); ++ Iterator iterator = items.iterator(); // CraftBukkit - decompile error + + while (iterator.hasNext()) { + T t0 = iterator.next(); +@@ -1705,7 +2141,7 @@ + object2intopenhashmap.addTo(s, 1); + } + +- return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry::getIntValue).reversed()).limit(5L).map((entry) -> { ++ return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry::getIntValue).reversed()).limit(5L).map((entry) -> { // CraftBukkit - decompile error + String s1 = (String) entry.getKey(); + + return s1 + ":" + entry.getIntValue(); +@@ -1717,6 +2153,7 @@ + + @Override + public LevelEntityGetter getEntities() { ++ org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot + return this.entityManager.getEntityGetter(); + } + +@@ -1800,7 +2237,28 @@ + + public GameRules getGameRules() { + return this.serverLevelData.getGameRules(); ++ } ++ ++ // Paper start - respect global sound events gamerule ++ public List getPlayersForGlobalSoundGamerule() { ++ return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players(); ++ } ++ ++ public double getGlobalSoundRangeSquared(java.util.function.Function rangeFunction) { ++ final double range = rangeFunction.apply(this.spigotConfig); ++ return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent ++ } ++ // Paper end - respect global sound events gamerule ++ // Paper start - notify observers even if grow failed ++ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) { ++ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) ++ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the ++ // tree grew or not ++ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { ++ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); ++ } + } ++ // Paper end - notify observers even if grow failed + + @Override + public CrashReportCategory fillReportDetails(CrashReport report) { +@@ -1828,22 +2286,30 @@ + } + + public void onTickingStart(Entity entity) { ++ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking + ServerLevel.this.entityTickList.add(entity); + } + + public void onTickingEnd(Entity entity) { + ServerLevel.this.entityTickList.remove(entity); ++ // Paper start - Reset pearls when they stop being ticked ++ if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { ++ pearl.cachedOwner = null; ++ pearl.ownerUUID = null; ++ } ++ // Paper end - Reset pearls when they stop being ticked + } + + public void onTrackingStart(Entity entity) { +- ServerLevel.this.getChunkSource().addEntity(entity); ++ org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer entityplayer) { + ServerLevel.this.players.add(entityplayer); + ServerLevel.this.updateSleepingPlayerList(); + } + + if (entity instanceof Mob entityinsentient) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String s = "onTrackingStart called during navigation iteration"; + + Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); +@@ -1864,9 +2330,58 @@ + } + + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); ++ entity.inWorld = true; // CraftBukkit - Mark entity as in world ++ entity.valid = true; // CraftBukkit ++ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server ++ // Paper start - Entity origin API ++ if (entity.getOriginVector() == null) { ++ entity.setOrigin(entity.getBukkitEntity().getLocation()); ++ } ++ // Default to current world if unknown, gross assumption but entities rarely change world ++ if (entity.getOriginWorld() == null) { ++ entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); ++ } ++ // Paper end - Entity origin API ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + public void onTrackingEnd(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ // Spigot start ++ if ( entity instanceof Player ) ++ { ++ com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> ++ { ++ for (Object o : worldData.cache.values() ) ++ { ++ if ( o instanceof MapItemSavedData ) ++ { ++ MapItemSavedData map = (MapItemSavedData) o; ++ map.carriedByPlayers.remove( (Player) entity ); ++ for ( Iterator iter = (Iterator) map.carriedBy.iterator(); iter.hasNext(); ) ++ { ++ if ( iter.next().player == entity ) ++ { ++ iter.remove(); ++ } ++ } ++ } ++ } ++ } ); ++ } ++ // Spigot end ++ // Spigot Start ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message ++ // Paper start - Fix merchant inventory not closing on entity removal ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { ++ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); ++ } ++ // Paper end - Fix merchant inventory not closing on entity removal ++ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ } ++ } ++ // Spigot End + ServerLevel.this.getChunkSource().removeEntity(entity); + if (entity instanceof ServerPlayer entityplayer) { + ServerLevel.this.players.remove(entityplayer); +@@ -1874,7 +2389,7 @@ + } + + if (entity instanceof Mob entityinsentient) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String s = "onTrackingStart called during navigation iteration"; + + Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); +@@ -1895,10 +2410,27 @@ + } + + entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); ++ // CraftBukkit start ++ entity.valid = false; ++ if (!(entity instanceof ServerPlayer)) { ++ for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players ++ player.getBukkitEntity().onEntityRemove(entity); ++ } ++ } ++ // CraftBukkit end ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + public void onSectionChange(Entity entity) { + entity.updateDynamicGameEventListener(DynamicGameEventListener::move); + } + } ++ ++ // Paper start - check global player list where appropriate ++ @Override ++ @Nullable ++ public Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.server.getPlayerList().getPlayer(uuid); ++ } ++ // Paper end - check global player list where appropriate + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch new file mode 100644 index 0000000000..9daf2af366 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -0,0 +1,1891 @@ +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -103,10 +103,6 @@ + import net.minecraft.util.Unit; + import net.minecraft.util.profiling.Profiler; + import net.minecraft.util.profiling.ProfilerFiller; +-import net.minecraft.world.Container; +-import net.minecraft.world.Difficulty; +-import net.minecraft.world.InteractionHand; +-import net.minecraft.world.MenuProvider; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.damagesource.DamageTypes; + import net.minecraft.world.effect.MobEffectInstance; +@@ -135,15 +131,16 @@ + import net.minecraft.world.entity.player.ChatVisiblity; + import net.minecraft.world.entity.player.Input; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.entity.vehicle.AbstractBoat; + import net.minecraft.world.entity.vehicle.AbstractMinecart; ++import net.minecraft.world.food.FoodData; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.ContainerListener; + import net.minecraft.world.inventory.ContainerSynchronizer; + import net.minecraft.world.inventory.HorseInventoryMenu; ++import net.minecraft.world.inventory.InventoryMenu; + import net.minecraft.world.inventory.ResultSlot; + import net.minecraft.world.inventory.Slot; + import net.minecraft.world.item.Item; +@@ -154,8 +151,6 @@ + import net.minecraft.world.item.WrittenBookItem; + import net.minecraft.world.item.crafting.Recipe; + import net.minecraft.world.item.crafting.RecipeHolder; +-import net.minecraft.world.item.enchantment.EnchantmentHelper; +-import net.minecraft.world.item.trading.MerchantOffers; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; +@@ -163,12 +158,14 @@ + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.BedBlock; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.ChestBlock; + import net.minecraft.world.level.block.HorizontalDirectionalBlock; + import net.minecraft.world.level.block.RespawnAnchorBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.CommandBlockEntity; + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.level.saveddata.maps.MapId; +@@ -179,11 +176,48 @@ + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.ScoreAccess; + import net.minecraft.world.scores.ScoreHolder; ++import org.slf4j.Logger; ++import net.minecraft.world.Container; ++import net.minecraft.world.Difficulty; ++import net.minecraft.world.InteractionHand; ++import net.minecraft.world.MenuProvider; ++// CraftBukkit start ++import net.minecraft.world.damagesource.CombatTracker; ++import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; ++import net.minecraft.world.item.enchantment.EnchantmentHelper; ++import net.minecraft.world.item.trading.MerchantOffers; ++import net.minecraft.world.scores.Scoreboard; + import net.minecraft.world.scores.Team; + import net.minecraft.world.scores.criteria.ObjectiveCriteria; +-import org.slf4j.Logger; ++import io.papermc.paper.adventure.PaperAdventure; // Paper ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.WeatherType; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.CraftWorldBorder; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftDimensionUtil; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.EntityExhaustionEvent; ++import org.bukkit.event.player.PlayerBedLeaveEvent; ++import org.bukkit.event.player.PlayerChangedMainHandEvent; ++import org.bukkit.event.player.PlayerChangedWorldEvent; ++import org.bukkit.event.player.PlayerDropItemEvent; ++import org.bukkit.event.player.PlayerLocaleChangeEvent; ++import org.bukkit.event.player.PlayerPortalEvent; ++import org.bukkit.event.player.PlayerRespawnEvent; ++import org.bukkit.event.player.PlayerSpawnChangeEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; ++import org.bukkit.inventory.MainHand; ++// CraftBukkit end + +-public class ServerPlayer extends Player { ++public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; +@@ -225,7 +259,8 @@ + private int levitationStartTime; + private boolean disconnected; + private int requestedViewDistance; +- public String language; ++ public String language = null; // CraftBukkit - default // Paper - default to null ++ public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + @Nullable + private Vec3 startingToFallPosition; + @Nullable +@@ -258,7 +293,35 @@ + private final CommandSource commandSource; + private int containerCounter; + public boolean wonGame; ++ private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed ++ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket; ++ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; ++ // Paper end - cancellable death event + ++ // CraftBukkit start ++ public CraftPlayer.TransferCookieConnection transferCookieConnection; ++ public String displayName; ++ public net.kyori.adventure.text.Component adventure$displayName; // Paper ++ public Component listName; ++ public int listOrder = 0; ++ 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; ++ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready ++ // CraftBukkit end ++ public boolean isRealPlayer; // Paper ++ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent ++ public @Nullable String clientBrandName = null; // Paper - Brand support ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event ++ + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); + this.chatVisibility = ChatVisiblity.FULL; +@@ -266,7 +329,7 @@ + this.canChatColor = true; + this.lastActionTime = Util.getMillis(); + this.requestedViewDistance = 2; +- this.language = "en_us"; ++ this.language = null; // Paper - default to null + this.lastSectionPos = SectionPos.of(0, 0, 0); + this.chunkTrackingView = ChunkTrackingView.EMPTY; + this.respawnDimension = Level.OVERWORLD; +@@ -285,7 +348,14 @@ + + } + ++ // Paper start - Sync offhand slot in menus + @Override ++ public void sendOffHandSlotChange() { ++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); ++ } ++ // Paper end - Sync offhand slot in menus ++ ++ @Override + public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { + ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); + } +@@ -316,6 +386,25 @@ + + } + } ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ @Override ++ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ Slot slot = handler.getSlot(slotId); ++ if (!(slot instanceof ResultSlot)) { ++ if (slot.container == ServerPlayer.this.getInventory()) { ++ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ return; ++ } ++ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); ++ event.callEvent(); ++ if (event.shouldTriggerAdvancements()) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ } ++ } ++ } ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent + + @Override + public void dataChanged(AbstractContainerMenu handler, int property, int value) {} +@@ -340,6 +429,13 @@ + public void sendSystemMessage(Component message) { + ServerPlayer.this.sendSystemMessage(message); + } ++ ++ // CraftBukkit start ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return ServerPlayer.this.getBukkitEntity(); ++ } ++ // CraftBukkit end + }; + this.textFilter = server.createTextFilterForPlayer(this); + this.gameMode = server.createGameModeForPlayer(this); +@@ -349,17 +445,72 @@ + this.server = server; + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); +- this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); +- this.updateOptions(clientOptions); ++ // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn ++ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login + this.object = null; ++ ++ // CraftBukkit start ++ this.displayName = this.getScoreboardName(); ++ this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper ++ this.bukkitPickUpLoot = true; ++ this.maxHealthCache = this.getMaxHealth(); ++ } ++ ++ // Use method to resend items in hands in case of client desync, because the item use got cancelled. ++ // For example, when cancelling the leash event ++ @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all ++ public void resendItemInHands() { ++ this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> { ++ this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem()); ++ }); ++ this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem()); ++ } ++ ++ // Yes, this doesn't match Vanilla, but it's the best we can do for now. ++ // If this is an issue, PRs are welcome ++ public final BlockPos getSpawnPoint(ServerLevel worldserver) { ++ BlockPos blockposition = worldserver.getSharedSpawnPos(); ++ ++ if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) { ++ int i = Math.max(0, this.server.getSpawnRadius(worldserver)); ++ int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ())); ++ ++ if (j < i) { ++ i = j; ++ } ++ ++ if (j <= 1) { ++ i = 1; ++ } ++ ++ long k = (long) (i * 2 + 1); ++ long l = k * k; ++ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; ++ int j1 = this.getCoprime(i1); ++ int k1 = RandomSource.create().nextInt(i1); ++ ++ for (int l1 = 0; l1 < i1; ++l1) { ++ int i2 = (k1 + j1 * l1) % i1; ++ int j2 = i2 % (i * 2 + 1); ++ int k2 = i2 / (i * 2 + 1); ++ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i); ++ ++ if (blockposition1 != null) { ++ return blockposition1; ++ } ++ } ++ } ++ ++ return blockposition; + } ++ // CraftBukkit end + + @Override + public BlockPos adjustSpawnLocation(ServerLevel world, BlockPos basePos) { + AABB axisalignedbb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO); + BlockPos blockposition1 = basePos; + +- if (world.dimensionType().hasSkyLight() && world.getServer().getWorldData().getGameType() != GameType.ADVENTURE) { ++ if (world.dimensionType().hasSkyLight() && world.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit + int i = Math.max(0, this.server.getSpawnRadius(world)); + int j = Mth.floor(world.getWorldBorder().getDistanceToBorder((double) basePos.getX(), (double) basePos.getZ())); + +@@ -395,14 +546,20 @@ + + Objects.requireNonNull(basePos); + crashreportsystemdetails.setDetail("Origin", basePos::toString); ++ // CraftBukkit start - decompile error ++ int finalI = i; + crashreportsystemdetails.setDetail("Radius", () -> { +- return Integer.toString(i); ++ return Integer.toString(finalI); ++ // CraftBukkit end + }); + crashreportsystemdetails.setDetail("Candidate", () -> { + return "[" + l2 + "," + i3 + "]"; + }); ++ // CraftBukkit start - decompile error ++ int finalL1 = l1; + crashreportsystemdetails.setDetail("Progress", () -> { +- return "" + l1 + " out of " + i1; ++ return "" + finalL1 + " out of " + i1; ++ // CraftBukkit end + }); + throw new ReportedException(crashreport); + } +@@ -440,7 +597,7 @@ + dataresult = WardenSpawnTracker.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("warden_spawn_tracker"))); + logger = ServerPlayer.LOGGER; + Objects.requireNonNull(logger); +- dataresult.resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> { ++ ((DataResult) dataresult).resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> { + this.wardenSpawnTracker = wardenspawntracker; + }); + } +@@ -457,17 +614,26 @@ + return this.server.getRecipeManager().byKey(resourcekey).isPresent(); + }); + } ++ this.getBukkitEntity().readExtraData(nbt); // CraftBukkit + + if (this.isSleeping()) { + this.stopSleeping(); + } + ++ // CraftBukkit start ++ String spawnWorld = nbt.getString("SpawnWorld"); ++ CraftWorld oldWorld = (CraftWorld) Bukkit.getWorld(spawnWorld); ++ if (oldWorld != null) { ++ this.respawnDimension = oldWorld.getHandle().dimension(); ++ } ++ // CraftBukkit end ++ + if (nbt.contains("SpawnX", 99) && nbt.contains("SpawnY", 99) && nbt.contains("SpawnZ", 99)) { + this.respawnPosition = new BlockPos(nbt.getInt("SpawnX"), nbt.getInt("SpawnY"), nbt.getInt("SpawnZ")); + this.respawnForced = nbt.getBoolean("SpawnForced"); + this.respawnAngle = nbt.getFloat("SpawnAngle"); + if (nbt.contains("SpawnDimension")) { +- DataResult dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension")); ++ DataResult> dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension")); // CraftBukkit - decompile error + Logger logger1 = ServerPlayer.LOGGER; + + Objects.requireNonNull(logger1); +@@ -482,7 +648,7 @@ + dataresult = BlockPos.CODEC.parse(NbtOps.INSTANCE, nbtbase); + logger = ServerPlayer.LOGGER; + Objects.requireNonNull(logger); +- dataresult.resultOrPartial(logger::error).ifPresent((blockposition) -> { ++ ((DataResult) dataresult).resultOrPartial(logger::error).ifPresent((blockposition) -> { // CraftBukkit - decompile error + this.raidOmenPosition = blockposition; + }); + } +@@ -492,7 +658,7 @@ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); +- DataResult dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); ++ DataResult dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); // CraftBukkit - decompile error + Logger logger = ServerPlayer.LOGGER; + + Objects.requireNonNull(logger); +@@ -526,6 +692,7 @@ + nbt.put("SpawnDimension", nbtbase); + }); + } ++ this.getBukkitEntity().setExtraData(nbt); // CraftBukkit + + nbt.putBoolean("spawn_extra_particles_on_fall", this.spawnExtraParticlesOnFall); + if (this.raidOmenPosition != null) { +@@ -544,7 +711,20 @@ + Entity entity = this.getRootVehicle(); + Entity entity1 = this.getVehicle(); + +- if (entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { ++ // CraftBukkit start - handle non-persistent vehicles ++ boolean persistVehicle = true; ++ if (entity1 != null) { ++ Entity vehicle; ++ for (vehicle = entity1; vehicle != null; vehicle = vehicle.getVehicle()) { ++ if (!vehicle.persist) { ++ persistVehicle = false; ++ break; ++ } ++ } ++ } ++ ++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status ++ // CraftBukkit end + CompoundTag nbttagcompound1 = new CompoundTag(); + CompoundTag nbttagcompound2 = new CompoundTag(); + +@@ -564,7 +744,7 @@ + ServerLevel worldserver = (ServerLevel) world; + CompoundTag nbttagcompound = ((CompoundTag) nbt.get()).getCompound("RootVehicle"); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver, EntitySpawnReason.LOAD, (entity1) -> { +- return !worldserver.addWithUUID(entity1) ? null : entity1; ++ return !worldserver.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // CraftBukkit - decompile error // Paper - Entity#getEntitySpawnReason + }); + + if (entity == null) { +@@ -598,12 +778,12 @@ + + if (!this.isPassenger()) { + ServerPlayer.LOGGER.warn("Couldn't reattach entity to player"); +- entity.discard(); ++ entity.discard(null); // CraftBukkit - add Bukkit remove cause + iterator = entity.getIndirectPassengers().iterator(); + + while (iterator.hasNext()) { + entity1 = (Entity) iterator.next(); +- entity1.discard(); ++ entity1.discard(null); // CraftBukkit - add Bukkit remove cause + } + } + } +@@ -618,6 +798,7 @@ + + while (iterator.hasNext()) { + ThrownEnderpearl entityenderpearl = (ThrownEnderpearl) iterator.next(); ++ if (entityenderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) continue; // Paper - Allow using old ender pearl behavior + + if (entityenderpearl.isRemoved()) { + ServerPlayer.LOGGER.warn("Trying to save removed ender pearl, skipping"); +@@ -625,7 +806,7 @@ + CompoundTag nbttagcompound1 = new CompoundTag(); + + entityenderpearl.save(nbttagcompound1); +- DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location()); ++ DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location()); // CraftBukkit - decompile error + Logger logger = ServerPlayer.LOGGER; + + Objects.requireNonNull(logger); +@@ -651,7 +832,7 @@ + nbttaglist.forEach((nbtbase1) -> { + if (nbtbase1 instanceof CompoundTag nbttagcompound) { + if (nbttagcompound.contains("ender_pearl_dimension")) { +- DataResult dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension")); ++ DataResult> dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension")); // CraftBukkit - decompile error + Logger logger = ServerPlayer.LOGGER; + + Objects.requireNonNull(logger); +@@ -684,7 +865,30 @@ + } + } + ++ } ++ ++ // CraftBukkit start - World fallback code, either respawn location or global spawn ++ public void spawnIn(Level world) { ++ this.setLevel(world); ++ if (world == null) { ++ this.unsetRemoved(); ++ Vec3 position = null; ++ if (this.respawnDimension != null) { ++ world = this.server.getLevel(this.respawnDimension); ++ if (world != null && this.getRespawnPosition() != null) { ++ position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null); ++ } ++ } ++ if (world == null || position == null) { ++ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); ++ position = Vec3.atCenterOf(world.getSharedSpawnPos()); ++ } ++ this.setLevel(world); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet ++ } ++ this.gameMode.setLevel((ServerLevel) world); + } ++ // CraftBukkit end + + public void setExperiencePoints(int points) { + float f = (float) this.getXpNeededForNextLevel(); +@@ -744,6 +948,11 @@ + + @Override + public void tick() { ++ // CraftBukkit start ++ if (this.joining) { ++ this.joining = false; ++ } ++ // CraftBukkit end + this.tickClientLoadTimeout(); + this.gameMode.tick(); + this.wardenSpawnTracker.tick(); +@@ -751,9 +960,13 @@ + --this.invulnerableTime; + } + +- this.containerMenu.broadcastChanges(); +- if (!this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ if (--this.containerUpdateDelay <= 0) { ++ this.containerMenu.broadcastChanges(); ++ this.containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; ++ } ++ // Paper end - Configurable container update tick rate ++ if (this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -807,7 +1020,7 @@ + + public void doTick() { + try { +- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { ++ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + +@@ -820,7 +1033,7 @@ + } + + if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) { +- this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); ++ this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit + this.lastSentHealth = this.getHealth(); + this.lastSentFood = this.foodData.getFoodLevel(); + this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F; +@@ -851,6 +1064,12 @@ + this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float) this.lastRecordedExperience)); + } + ++ // CraftBukkit start - Force max health updates ++ if (this.maxHealthCache != this.getMaxHealth()) { ++ this.getBukkitEntity().updateScaledHealth(); ++ } ++ // CraftBukkit end ++ + if (this.experienceLevel != this.lastRecordedLevel) { + this.lastRecordedLevel = this.experienceLevel; + this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float) this.lastRecordedLevel)); +@@ -865,6 +1084,20 @@ + CriteriaTriggers.LOCATION.trigger(this); + } + ++ // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border ++ if (this.oldLevel == -1) { ++ this.oldLevel = this.experienceLevel; ++ } ++ ++ if (this.oldLevel != this.experienceLevel) { ++ CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel); ++ this.oldLevel = this.experienceLevel; ++ } ++ ++ if (this.getBukkitEntity().hasClientWorldBorder()) { ++ ((CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick(); ++ } ++ // CraftBukkit end + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking player"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Player being ticked"); +@@ -893,7 +1126,7 @@ + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) { + if (this.tickCount % 20 == 0) { + if (this.getHealth() < this.getMaxHealth()) { +- this.heal(1.0F); ++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit - added regain reason of "REGEN" for filtering purposes. + } + + float f = this.foodData.getSaturationLevel(); +@@ -946,19 +1179,105 @@ + } + + private void updateScoreForCriteria(ObjectiveCriteria criterion, int score) { +- this.getScoreboard().forAllObjectives(criterion, this, (scoreaccess) -> { ++ // CraftBukkit - Use our scores instead ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterion, this, (scoreaccess) -> { + scoreaccess.set(score); + }); + } + ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { ++ List itemsToKeep = event.getItemsToKeep(); ++ if (inv == null) { ++ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? ++ if (!itemsToKeep.isEmpty()) { ++ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { ++ event.getEntity().getInventory().addItem(itemStack); ++ } ++ } ++ ++ return; ++ } ++ ++ for (int i = 0; i < inv.size(); ++i) { ++ ItemStack item = inv.get(i); ++ if (EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.EMPTY); ++ continue; ++ } ++ ++ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); ++ boolean keep = false; ++ final Iterator iterator = itemsToKeep.iterator(); ++ while (iterator.hasNext()) { ++ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); ++ if (bukkitStack.equals(itemStack)) { ++ iterator.remove(); ++ keep = true; ++ break; ++ } ++ } ++ ++ if (!keep) { ++ inv.set(i, ItemStack.EMPTY); ++ } ++ } ++ } ++ // Paper end - PlayerDeathEvent#getItemsToKeep ++ + @Override + public void die(DamageSource damageSource) { +- this.gameEvent(GameEvent.ENTITY_DIE); ++ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check + boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); ++ // CraftBukkit start - fire PlayerDeathEvent ++ if (this.isRemoved()) { ++ return; ++ } ++ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior ++ boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); + +- if (flag) { +- Component ichatbasecomponent = this.getCombatTracker().getDeathMessage(); ++ if (!keepInventory) { ++ for (ItemStack item : this.getInventory().getContents()) { ++ if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) ++ } ++ } ++ } ++ if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false ++ // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) ++ this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0); ++ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove. + ++ loot.addAll(this.drops); ++ this.drops.clear(); // SPIGOT-5188: make sure to clear ++ } // Paper - fix player loottables running when mob loot gamerule is false ++ ++ Component defaultMessage = this.getCombatTracker().getDeathMessage(); ++ ++ String deathmessage = defaultMessage.getString(); ++ this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel ++ org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, damageSource, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure ++ // Paper start - cancellable death event ++ if (event.isCancelled()) { ++ // make compatible with plugins that might have already set the health in an event listener ++ if (this.getHealth() <= 0) { ++ this.setHealth((float) event.getReviveHealth()); ++ } ++ return; ++ } ++ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method ++ // Paper end ++ ++ // SPIGOT-943 - only call if they have an inventory open ++ if (this.containerMenu != this.inventoryMenu) { ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason ++ } ++ ++ net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure ++ ++ if (deathMessage != null && deathMessage != net.kyori.adventure.text.Component.empty() && flag) { // Paper - Adventure // TODO: allow plugins to override? ++ Component ichatbasecomponent = PaperAdventure.asVanilla(deathMessage); // Paper - Adventure ++ + this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), ichatbasecomponent), PacketSendListener.exceptionallySend(() -> { + boolean flag1 = true; + String s = ichatbasecomponent.getString(256); +@@ -988,12 +1307,23 @@ + if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_FORGIVE_DEAD_PLAYERS)) { + this.tellNeutralMobsThatIDied(); + } +- +- if (!this.isSpectator()) { +- this.dropAllDeathLoot(this.serverLevel(), damageSource); ++ // SPIGOT-5478 must be called manually now ++ if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), damageSource.getEntity()); // Paper - tie to event ++ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. ++ if (!event.getKeepInventory()) { ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ for (NonNullList inv : this.getInventory().compartments) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end - PlayerDeathEvent#getItemsToKeep + } + +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); ++ this.setCamera(this); // Remove spectated target ++ // CraftBukkit end ++ ++ // CraftBukkit - Get our scores instead ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); + LivingEntity entityliving = this.getKillCredit(); + + if (entityliving != null) { +@@ -1028,10 +1358,12 @@ + public void awardKillScore(Entity entityKilled, DamageSource damageSource) { + if (entityKilled != this) { + super.awardKillScore(entityKilled, damageSource); +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); +- if (entityKilled instanceof Player) { ++ // CraftBukkit - Get our scores instead ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); ++ if (entityKilled instanceof net.minecraft.world.entity.player.Player) { + this.awardStat(Stats.PLAYER_KILLS); +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); ++ // CraftBukkit - Get our scores instead ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); + } else { + this.awardStat(Stats.MOB_KILLS); + } +@@ -1049,7 +1381,8 @@ + int i = scoreboardteam.getColor().getId(); + + if (i >= 0 && i < criterions.length) { +- this.getScoreboard().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment); ++ // CraftBukkit - Get our scores instead ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment); + } + } + +@@ -1062,8 +1395,8 @@ + } else { + Entity entity = source.getEntity(); + +- if (entity instanceof Player) { +- Player entityhuman = (Player) entity; ++ if (entity instanceof net.minecraft.world.entity.player.Player) { ++ net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity; + + if (!this.canHarmPlayer(entityhuman)) { + return false; +@@ -1074,8 +1407,8 @@ + AbstractArrow entityarrow = (AbstractArrow) entity; + Entity entity1 = entityarrow.getOwner(); + +- if (entity1 instanceof Player) { +- Player entityhuman1 = (Player) entity1; ++ if (entity1 instanceof net.minecraft.world.entity.player.Player) { ++ net.minecraft.world.entity.player.Player entityhuman1 = (net.minecraft.world.entity.player.Player) entity1; + + if (!this.canHarmPlayer(entityhuman1)) { + return false; +@@ -1083,38 +1416,84 @@ + } + } + +- return super.hurtServer(world, source, amount); ++ // Paper start - cancellable death events ++ // return super.hurtServer(world, source, amount); ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.hurtServer(world, source, amount); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.connection.send(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end - cancellable death events + } + } + + @Override +- public boolean canHarmPlayer(Player player) { ++ public boolean canHarmPlayer(net.minecraft.world.entity.player.Player player) { + return !this.isPvpAllowed() ? false : super.canHarmPlayer(player); + } + + private boolean isPvpAllowed() { +- return this.server.isPvpAllowed(); ++ // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode ++ return this.level().pvpMode; + } + +- public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean alive, TeleportTransition.PostTeleportTransition postDimensionTransition) { ++ // CraftBukkit start ++ public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean flag, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerRespawnEvent.RespawnReason reason) { ++ TeleportTransition teleportTransition; ++ boolean isBedSpawn = false; ++ boolean isAnchorSpawn = false; ++ // CraftBukkit end + BlockPos blockposition = this.getRespawnPosition(); + float f = this.getRespawnAngle(); + boolean flag1 = this.isRespawnForced(); + ServerLevel worldserver = this.server.getLevel(this.getRespawnDimension()); + + if (worldserver != null && blockposition != null) { +- Optional optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, alive); ++ Optional optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, flag); + + if (optional.isPresent()) { + ServerPlayer.RespawnPosAngle entityplayer_respawnposangle = (ServerPlayer.RespawnPosAngle) optional.get(); + +- return new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, postDimensionTransition); ++ // CraftBukkit start ++ isBedSpawn = entityplayer_respawnposangle.isBedSpawn(); ++ isAnchorSpawn = entityplayer_respawnposangle.isAnchorSpawn(); ++ teleportTransition = new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, teleporttransition_a); ++ // CraftBukkit end + } else { +- return TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postDimensionTransition); ++ teleportTransition = TeleportTransition.missingRespawnBlock(this.server.overworld(), this, teleporttransition_a); // CraftBukkit + } + } else { +- return new TeleportTransition(this.server.overworld(), this, postDimensionTransition); ++ teleportTransition = new TeleportTransition(this.server.overworld(), this, teleporttransition_a); // CraftBukkit + } ++ // CraftBukkit start ++ if (reason == null) { ++ return teleportTransition; ++ } ++ ++ Player respawnPlayer = this.getBukkitEntity(); ++ Location location = CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot()); ++ ++ // Paper start - respawn flags ++ com.google.common.collect.ImmutableSet.Builder builder = com.google.common.collect.ImmutableSet.builder(); ++ if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) { ++ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); ++ } ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder); ++ // Paper end - respawn flags ++ this.level().getCraftServer().getPluginManager().callEvent(respawnEvent); ++ // Spigot Start ++ if (this.connection.isDisconnected()) { ++ return null; ++ } ++ // Spigot End ++ ++ location = respawnEvent.getRespawnLocation(); ++ ++ return new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), teleportTransition.deltaMovement(), location.getYaw(), location.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), teleportTransition.relatives(), teleportTransition.postTeleportTransition(), teleportTransition.cause()); ++ // CraftBukkit end + } + + public static Optional findRespawnAndUseSpawnBlock(ServerLevel world, BlockPos pos, float spawnAngle, boolean spawnForced, boolean alive) { +@@ -1129,11 +1508,11 @@ + } + + return optional.map((vec3d) -> { +- return ServerPlayer.RespawnPosAngle.of(vec3d, pos); ++ return ServerPlayer.RespawnPosAngle.of(vec3d, pos, false, true); // CraftBukkit + }); + } else if (block instanceof BedBlock && BedBlock.canSetSpawn(world)) { + return BedBlock.findStandUpPosition(EntityType.PLAYER, world, pos, (Direction) iblockdata.getValue(BedBlock.FACING), spawnAngle).map((vec3d) -> { +- return ServerPlayer.RespawnPosAngle.of(vec3d, pos); ++ return ServerPlayer.RespawnPosAngle.of(vec3d, pos, true, false); // CraftBukkit + }); + } else if (!spawnForced) { + return Optional.empty(); +@@ -1142,7 +1521,7 @@ + BlockState iblockdata1 = world.getBlockState(pos.above()); + boolean flag3 = iblockdata1.getBlock().isPossibleToRespawnInThis(iblockdata1); + +- return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle)) : Optional.empty(); ++ return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle, false, false)) : Optional.empty(); // CraftBukkit + } + } + +@@ -1160,6 +1539,7 @@ + @Nullable + @Override + public ServerPlayer teleport(TeleportTransition teleportTarget) { ++ if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 + if (this.isRemoved()) { + return null; + } else { +@@ -1169,39 +1549,78 @@ + + ServerLevel worldserver = teleportTarget.newLevel(); + ServerLevel worldserver1 = this.serverLevel(); +- ResourceKey resourcekey = worldserver1.dimension(); ++ // CraftBukkit start ++ ResourceKey resourcekey = worldserver1.getTypeKey(); + ++ Location enter = this.getBukkitEntity().getLocation(); ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ Location exit = /* (worldserver == null) ? null : // Paper - always non-null */CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause()); ++ // Paper start - gateway-specific teleport event ++ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { ++ tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity)); ++ } ++ // Paper end - gateway-specific teleport event ++ Bukkit.getServer().getPluginManager().callEvent(tpEvent); ++ Location newExit = tpEvent.getTo(); ++ if (tpEvent.isCancelled() || newExit == null) { ++ return null; ++ } ++ if (!newExit.equals(exit)) { ++ worldserver = ((CraftWorld) newExit.getWorld()).getHandle(); ++ teleportTarget = new TeleportTransition(worldserver, CraftLocation.toVec3D(newExit), Vec3.ZERO, newExit.getYaw(), newExit.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ } ++ // CraftBukkit end ++ + if (!teleportTarget.asPassenger()) { + this.stopRiding(); + } + +- if (worldserver.dimension() == resourcekey) { +- this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ // CraftBukkit start ++ if (worldserver != null && worldserver.dimension() == worldserver1.dimension()) { ++ this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ // CraftBukkit end + this.connection.resetPosition(); + teleportTarget.postTeleportTransition().onTransition(this); + return this; + } else { ++ // CraftBukkit start ++ /* + this.isChangingDimension = true; +- LevelData worlddata = worldserver.getLevelData(); ++ WorldData worlddata = worldserver.getLevelData(); + +- this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); +- this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3)); ++ this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); + PlayerList playerlist = this.server.getPlayerList(); + + playerlist.sendPlayerPermissionLevel(this); + worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + this.unsetRemoved(); ++ */ ++ // CraftBukkit end + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("moving"); +- if (resourcekey == Level.OVERWORLD && worldserver.dimension() == Level.NETHER) { ++ if (worldserver != null && resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event + this.enteredNetherPosition = this.position(); + } + + gameprofilerfiller.pop(); + gameprofilerfiller.push("placing"); ++ // CraftBukkit start ++ this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds ++ LevelData worlddata = worldserver.getLevelData(); ++ ++ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); ++ this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ PlayerList playerlist = this.server.getPlayerList(); ++ ++ playerlist.sendPlayerPermissionLevel(this); ++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); ++ this.unsetRemoved(); ++ // CraftBukkit end + this.setServerLevel(worldserver); +- this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event + this.connection.resetPosition(); + worldserver.addDuringTeleport(this); + gameprofilerfiller.pop(); +@@ -1215,10 +1634,33 @@ + this.lastSentExp = -1; + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; ++ ++ // CraftBukkit start ++ PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); ++ this.level().getCraftServer().getPluginManager().callEvent(changeEvent); ++ // CraftBukkit end ++ // Paper start - Reset shield blocking on dimension change ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end - Reset shield blocking on dimension change + return this; + } ++ } ++ } ++ ++ // CraftBukkit start ++ @Override ++ public CraftPortalEvent callPortalEvent(Entity entity, Location exit, TeleportCause cause, int searchRadius, int creationRadius) { ++ Location enter = this.getBukkitEntity().getLocation(); ++ PlayerPortalEvent event = new PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) { ++ return null; + } ++ return new CraftPortalEvent(event); + } ++ // CraftBukkit end + + @Override + public void forceSetRotation(float yaw, float pitch) { +@@ -1228,13 +1670,27 @@ + public void triggerDimensionChangeTriggers(ServerLevel origin) { + ResourceKey resourcekey = origin.dimension(); + ResourceKey resourcekey1 = this.level().dimension(); ++ // CraftBukkit start ++ ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); ++ ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); + +- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); +- if (resourcekey == Level.NETHER && resourcekey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { ++ // Paper start - Add option for strict advancement dimension checks ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { ++ maindimensionkey = resourcekey; ++ maindimensionkey1 = resourcekey1; ++ } ++ // Paper end - Add option for strict advancement dimension checks ++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); ++ if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { ++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); ++ } ++ ++ if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { ++ // CraftBukkit end + CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition); + } + +- if (resourcekey1 != Level.NETHER) { ++ if (maindimensionkey1 != Level.NETHER) { // CraftBukkit + this.enteredNetherPosition = null; + } + +@@ -1251,36 +1707,63 @@ + this.containerMenu.broadcastChanges(); + } + +- @Override +- public Either startSleepInBed(BlockPos pos) { +- Direction enumdirection = (Direction) this.level().getBlockState(pos).getValue(HorizontalDirectionalBlock.FACING); +- ++ // CraftBukkit start - moved bed result checks from below into separate method ++ private Either getBedResult(BlockPos blockposition, Direction enumdirection) { + if (!this.isSleeping() && this.isAlive()) { +- if (!this.level().dimensionType().natural()) { +- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE); +- } else if (!this.bedInRange(pos, enumdirection)) { +- return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY); +- } else if (this.bedBlocked(pos, enumdirection)) { +- return Either.left(Player.BedSleepingProblem.OBSTRUCTED); ++ if (!this.level().dimensionType().natural() || !this.level().dimensionType().bedWorks()) { ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_HERE); ++ } else if (!this.bedInRange(blockposition, enumdirection)) { ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY); ++ } else if (this.bedBlocked(blockposition, enumdirection)) { ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED); + } else { +- this.setRespawnPosition(this.level().dimension(), pos, this.getYRot(), false, true); ++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent + if (this.level().isDay()) { +- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW); + } else { + if (!this.isCreative()) { + double d0 = 8.0D; + double d1 = 5.0D; +- Vec3 vec3d = Vec3.atBottomCenterOf(pos); ++ Vec3 vec3d = Vec3.atBottomCenterOf(blockposition); + List list = this.level().getEntitiesOfClass(Monster.class, new AABB(vec3d.x() - 8.0D, vec3d.y() - 5.0D, vec3d.z() - 8.0D, vec3d.x() + 8.0D, vec3d.y() + 5.0D, vec3d.z() + 8.0D), (entitymonster) -> { + return entitymonster.isPreventingPlayerRest(this.serverLevel(), this); + }); + + if (!list.isEmpty()) { +- return Either.left(Player.BedSleepingProblem.NOT_SAFE); ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE); + } + } + +- Either either = super.startSleepInBed(pos).ifRight((unit) -> { ++ return Either.right(Unit.INSTANCE); ++ } ++ } ++ } else { ++ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM); ++ } ++ } ++ ++ @Override ++ public Either startSleepInBed(BlockPos blockposition, boolean force) { ++ Direction enumdirection = (Direction) this.level().getBlockState(blockposition).getValue(HorizontalDirectionalBlock.FACING); ++ Either bedResult = this.getBedResult(blockposition, enumdirection); ++ ++ if (bedResult.left().orElse(null) == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { ++ return bedResult; // return immediately if the result is not bypassable by plugins ++ } ++ ++ if (force) { ++ bedResult = Either.right(Unit.INSTANCE); ++ } ++ ++ bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, blockposition, bedResult); ++ if (bedResult.left().isPresent()) { ++ return bedResult; ++ } ++ ++ { ++ { ++ { ++ Either either = super.startSleepInBed(blockposition, force).ifRight((unit) -> { + this.awardStat(Stats.SLEEP_IN_BED); + CriteriaTriggers.SLEPT_IN_BED.trigger(this); + }); +@@ -1293,9 +1776,8 @@ + return either; + } + } +- } else { +- return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM); + } ++ // CraftBukkit end + } + + @Override +@@ -1322,13 +1804,31 @@ + + @Override + public void stopSleepInBed(boolean skipSleepTimer, boolean updateSleepingPlayers) { ++ if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one! ++ // CraftBukkit start - fire PlayerBedLeaveEvent ++ CraftPlayer player = this.getBukkitEntity(); ++ BlockPos bedPosition = this.getSleepingPos().orElse(null); ++ ++ org.bukkit.block.Block bed; ++ if (bedPosition != null) { ++ bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ()); ++ } else { ++ bed = this.level().getWorld().getBlockAt(player.getLocation()); ++ } ++ ++ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed, true); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + if (this.isSleeping()) { + this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2)); + } + + super.stopSleepInBed(skipSleepTimer, updateSleepingPlayers); + if (this.connection != null) { +- this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), TeleportCause.EXIT_BED); // CraftBukkit + } + + } +@@ -1341,7 +1841,7 @@ + + @Override + public boolean isInvulnerableTo(ServerLevel world, DamageSource source) { +- return super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded(); ++ return (super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && source.is(DamageTypes.CRAMMING)); // Paper - disable player cramming + } + + @Override +@@ -1387,8 +1887,9 @@ + this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front)); + } + +- public void nextContainerCounter() { ++ public int nextContainerCounter() { // CraftBukkit - void -> int + this.containerCounter = this.containerCounter % 100 + 1; ++ return this.containerCounter; // CraftBukkit + } + + @Override +@@ -1396,13 +1897,44 @@ + if (factory == null) { + return OptionalInt.empty(); + } else { ++ // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...) ++ /* + if (this.containerMenu != this.inventoryMenu) { + this.closeContainer(); + } ++ */ ++ // CraftBukkit end + + this.nextContainerCounter(); + AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); + ++ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent ++ // CraftBukkit start - Inventory open hook ++ if (container != null) { ++ container.setTitle(factory.getDisplayName()); ++ ++ boolean cancelled = false; ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); ++ container = result.getSecond(); ++ title = PaperAdventure.asVanilla(result.getFirst()); ++ // Paper end - Add titleOverride to InventoryOpenEvent ++ if (container == null && !cancelled) { // Let pre-cancelled events fall through ++ // SPIGOT-5263 - close chest if cancelled ++ if (factory instanceof Container) { ++ ((Container) factory).stopOpen(this); ++ } else if (factory instanceof ChestBlock.DoubleInventory) { ++ // SPIGOT-5355 - double chests too :( ++ ((ChestBlock.DoubleInventory) factory).inventorylargechest.stopOpen(this); ++ // Paper start - Fix InventoryOpenEvent cancellation ++ } else if (!this.enderChestInventory.isActiveChest(null)) { ++ this.enderChestInventory.stopOpen(this); ++ // Paper end - Fix InventoryOpenEvent cancellation ++ } ++ return OptionalInt.empty(); ++ } ++ } ++ // CraftBukkit end + if (container == null) { + if (this.isSpectator()) { + this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true); +@@ -1410,9 +1942,11 @@ + + return OptionalInt.empty(); + } else { +- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), factory.getDisplayName())); +- this.initMenu(container); ++ // CraftBukkit start + this.containerMenu = container; ++ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent ++ // CraftBukkit end ++ this.initMenu(container); + return OptionalInt.of(this.containerCounter); + } + } +@@ -1425,15 +1959,26 @@ + + @Override + public void openHorseInventory(AbstractHorse horse, Container inventory) { ++ // CraftBukkit start - Inventory open hook ++ this.nextContainerCounter(); ++ AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns()); ++ container.setTitle(horse.getDisplayName()); ++ container = CraftEventFactory.callInventoryOpenEvent(this, container); ++ ++ if (container == null) { ++ inventory.stopOpen(this); ++ return; ++ } ++ // CraftBukkit end + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + +- this.nextContainerCounter(); ++ // this.nextContainerCounter(); // CraftBukkit - moved up + int i = horse.getInventoryColumns(); + + this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, i, horse.getId())); +- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, i); ++ this.containerMenu = container; // CraftBukkit + this.initMenu(this.containerMenu); + } + +@@ -1456,9 +2001,28 @@ + + @Override + public void closeContainer() { ++ // Paper start - Inventory close reason ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ @Override ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } ++ // Paper start - special close for unloaded inventory ++ @Override ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // copied from above ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end ++ // copied from below ++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); ++ this.containerMenu = this.inventoryMenu; ++ // do not run close logic ++ } ++ // Paper end - special close for unloaded inventory + + @Override + public void doCloseContainer() { +@@ -1485,19 +2049,19 @@ + i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F); + if (i > 0) { + this.awardStat(Stats.SWIM_ONE_CM, i); +- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isEyeInFluid(FluidTags.WATER)) { + i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F); + if (i > 0) { + this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i); +- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isInWater()) { + i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaZ * deltaZ) * 100.0F); + if (i > 0) { + this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i); +- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.onClimbable()) { + if (deltaY > 0.0D) { +@@ -1508,13 +2072,13 @@ + if (i > 0) { + if (this.isSprinting()) { + this.awardStat(Stats.SPRINT_ONE_CM, i); +- this.causeFoodExhaustion(0.1F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.sprintMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot + } else if (this.isCrouching()) { + this.awardStat(Stats.CROUCH_ONE_CM, i); +- this.causeFoodExhaustion(0.0F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent // Spigot + } else { + this.awardStat(Stats.WALK_ONE_CM, i); +- this.causeFoodExhaustion(0.0F * (float) i * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } + } else if (this.isFallFlying()) { +@@ -1557,7 +2121,7 @@ + @Override + public void awardStat(Stat stat, int amount) { + this.stats.increment(this, stat, amount); +- this.getScoreboard().forAllObjectives(stat, this, (scoreaccess) -> { ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, (scoreaccess) -> { + scoreaccess.add(amount); + }); + } +@@ -1565,7 +2129,7 @@ + @Override + public void resetStat(Stat stat) { + this.stats.setValue(this, stat, 0); +- this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead + } + + @Override +@@ -1597,9 +2161,9 @@ + super.jumpFromGround(); + this.awardStat(Stats.JUMP); + if (this.isSprinting()) { +- this.causeFoodExhaustion(0.2F); ++ this.causeFoodExhaustion(this.level().spigotConfig.jumpSprintExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.causeFoodExhaustion(0.05F); ++ this.causeFoodExhaustion(this.level().spigotConfig.jumpWalkExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } + + } +@@ -1613,6 +2177,13 @@ + public void disconnect() { + this.disconnected = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount ++ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount ++ + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } +@@ -1625,6 +2196,7 @@ + + public void resetSentInfo() { + this.lastSentHealth = -1.0E8F; ++ this.lastSentExp = -1; // CraftBukkit - Added to reset + } + + @Override +@@ -1661,7 +2233,7 @@ + this.onUpdateAbilities(); + if (alive) { + this.getAttributes().assignBaseValues(oldPlayer.getAttributes()); +- this.getAttributes().assignPermanentModifiers(oldPlayer.getAttributes()); ++ // this.getAttributes().assignPermanentModifiers(entityplayer.getAttributes()); // CraftBukkit + this.setHealth(oldPlayer.getHealth()); + this.foodData = oldPlayer.foodData; + Iterator iterator = oldPlayer.getActiveEffects().iterator(); +@@ -1669,7 +2241,7 @@ + while (iterator.hasNext()) { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + +- this.addEffect(new MobEffectInstance(mobeffect)); ++ // this.addEffect(new MobEffect(mobeffect)); // CraftBukkit + } + + this.getInventory().replaceWith(oldPlayer.getInventory()); +@@ -1680,7 +2252,7 @@ + this.portalProcess = oldPlayer.portalProcess; + } else { + this.getAttributes().assignBaseValues(oldPlayer.getAttributes()); +- this.setHealth(this.getMaxHealth()); ++ // this.setHealth(this.getMaxHealth()); // CraftBukkit + if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || oldPlayer.isSpectator()) { + this.getInventory().replaceWith(oldPlayer.getInventory()); + this.experienceLevel = oldPlayer.experienceLevel; +@@ -1696,7 +2268,7 @@ + this.lastSentExp = -1; + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; +- this.recipeBook.copyOverData(oldPlayer.recipeBook); ++ // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit + this.seenCredits = oldPlayer.seenCredits; + this.enteredNetherPosition = oldPlayer.enteredNetherPosition; + this.chunkTrackingView = oldPlayer.chunkTrackingView; +@@ -1752,19 +2324,19 @@ + } + + @Override +- public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set flags, float yaw, float pitch, boolean resetCamera) { ++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set set, float f, float f1, boolean flag, TeleportCause cause) { // CraftBukkit + if (this.isSleeping()) { + this.stopSleepInBed(true, true); + } + +- if (resetCamera) { ++ if (flag) { + this.setCamera(this); + } + +- boolean flag1 = super.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera); ++ boolean flag1 = super.teleportTo(worldserver, d0, d1, d2, set, f, f1, flag, cause); // CraftBukkit + + if (flag1) { +- this.setYHeadRot(flags.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw); ++ this.setYHeadRot(set.contains(Relative.Y_ROT) ? this.getYHeadRot() + f : f); + } + + return flag1; +@@ -1799,10 +2371,18 @@ + } + + public boolean setGameMode(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ @Nullable ++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { + boolean flag = this.isSpectator(); + +- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { +- return false; ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); ++ if (event == null || event.isCancelled()) { ++ return null; ++ // Paper end - Expand PlayerGameModeChangeEvent + } else { + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { +@@ -1818,7 +2398,7 @@ + + this.onUpdateAbilities(); + this.updateEffectVisibility(); +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +@@ -1861,8 +2441,13 @@ + } + + public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params) { ++ // Paper start ++ this.sendChatMessage(message, filterMaskEnabled, params, null); ++ } ++ public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params, @Nullable Component unsigned) { ++ // Paper end + if (this.acceptsChatMessages()) { +- message.sendToPlayer(this, filterMaskEnabled, params); ++ message.sendToPlayer(this, filterMaskEnabled, params, unsigned); // Paper + } + + } +@@ -1878,7 +2463,36 @@ + } + + public void updateOptions(ClientInformation clientOptions) { ++ // Paper start - settings event ++ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> { ++ map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientOptions.language()); ++ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientOptions.viewDistance()); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name())); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientOptions.chatColors()); ++ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation())); ++ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); ++ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientOptions.textFilteringEnabled()); ++ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientOptions.allowsListing()); ++ map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientOptions.particleStatus().name())); ++ })).callEvent(); ++ // Paper end - settings event ++ // CraftBukkit start ++ if (this.getMainArm() != clientOptions.mainHand()) { ++ PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); ++ this.server.server.getPluginManager().callEvent(event); ++ } ++ if (this.language == null || !this.language.equals(clientOptions.language())) { // Paper ++ PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(this.getBukkitEntity(), clientOptions.language()); ++ this.server.server.getPluginManager().callEvent(event); ++ } ++ // CraftBukkit end ++ // Paper start - don't call options events on login ++ this.updateOptionsNoEvents(clientOptions); ++ } ++ public void updateOptionsNoEvents(ClientInformation clientOptions) { ++ // Paper end + this.language = clientOptions.language(); ++ this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper + this.requestedViewDistance = clientOptions.viewDistance(); + this.chatVisibility = clientOptions.chatVisibility(); + this.canChatColor = clientOptions.chatColors(); +@@ -1957,12 +2571,27 @@ + + this.camera = (Entity) (entity == null ? this : entity); + if (entity1 != this.camera) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity ++ if (this.camera == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } ++ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity + Level world = this.camera.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false); ++ this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, TeleportCause.SPECTATE); // CraftBukkit + } + + if (entity != null) { +@@ -1999,11 +2628,11 @@ + + @Nullable + public Component getTabListDisplayName() { +- return null; ++ return this.listName; // CraftBukkit + } + + public int getTabListOrder() { +- return 0; ++ return this.listOrder; // CraftBukkit + } + + @Override +@@ -2045,12 +2674,44 @@ + this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false); + } + ++ @Deprecated // Paper - Add PlayerSetSpawnEvent + public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { ++ // Paper start - Add PlayerSetSpawnEvent ++ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); ++ } ++ @Deprecated ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { ++ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); ++ } ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { ++ Location spawnLoc = null; ++ boolean willNotify = false; + if (pos != null) { + boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); ++ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); ++ spawnLoc.setYaw(angle); ++ willNotify = sendMessage && !flag2; ++ } + +- if (sendMessage && !flag2) { +- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); ++ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, ++ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); ++ dumbEvent.callEvent(); ++ ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); ++ event.setCancelled(dumbEvent.isCancelled()); ++ if (!event.callEvent()) { ++ return false; ++ } ++ if (event.getLocation() != null) { ++ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); ++ angle = event.getLocation().getYaw(); ++ forced = event.isForced(); ++ // Paper end - Add PlayerSetSpawnEvent ++ ++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent ++ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent + } + + this.respawnPosition = pos; +@@ -2064,6 +2725,7 @@ + this.respawnForced = false; + } + ++ return true; // Paper - Add PlayerSetSpawnEvent + } + + public SectionPos getLastSectionPos() { +@@ -2088,18 +2750,44 @@ + } + + @Override +- public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { +- ItemEntity entityitem = this.createItemStackToDrop(stack, throwRandomly, retainOwnership); ++ public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) { // CraftBukkit - SPIGOT-2942: Add boolean to call event ++ ItemEntity entityitem = this.createItemStackToDrop(itemstack, flag, flag1); + + if (entityitem == null) { + return null; + } else { ++ // CraftBukkit start - fire PlayerDropItemEvent ++ if (callEvent) { ++ Player player = (Player) this.getBukkitEntity(); ++ org.bukkit.entity.Item drop = (org.bukkit.entity.Item) entityitem.getBukkitEntity(); ++ ++ PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); ++ if (flag1 && (cur == null || cur.getAmount() == 0)) { ++ // The complete stack was dropped ++ player.getInventory().setItemInHand(drop.getItemStack()); ++ } else if (flag1 && 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 ++ + this.level().addFreshEntity(entityitem); + ItemStack itemstack1 = entityitem.getItem(); + +- if (retainOwnership) { ++ if (flag1) { + if (!itemstack1.isEmpty()) { +- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item + } + + this.awardStat(Stats.DROP); +@@ -2115,6 +2803,11 @@ + return null; + } else { + double d0 = this.getEyeY() - 0.30000001192092896D; ++ // Paper start ++ ItemStack tmp = stack.copy(); ++ stack.setCount(0); ++ stack = tmp; ++ // Paper end + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), stack); + + entityitem.setPickUpDelay(40); +@@ -2166,6 +2859,16 @@ + } + + public void loadGameTypes(@Nullable CompoundTag nbt) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { ++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); ++ } else { ++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); ++ } ++ return; ++ } ++ // Paper end - Expand PlayerGameModeChangeEvent + this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); + } + +@@ -2275,9 +2978,15 @@ + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation + if (entity instanceof LivingEntity entityliving) { + Iterator iterator = entityliving.getActiveEffects().iterator(); + +@@ -2371,20 +3080,165 @@ + } + + public static long placeEnderPearlTicket(ServerLevel world, ChunkPos chunkPos) { +- world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos); ++ if (!world.paperConfig().misc.legacyEnderPearlBehavior) world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos); // Paper - Allow using old ender pearl behavior + return TicketType.ENDER_PEARL.timeout(); + } + +- public static record RespawnPosAngle(Vec3 position, float yaw) { ++ // CraftBukkit start ++ public static record RespawnPosAngle(Vec3 position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn) { + +- public static ServerPlayer.RespawnPosAngle of(Vec3 respawnPos, BlockPos currentPos) { +- return new ServerPlayer.RespawnPosAngle(respawnPos, calculateLookAtYaw(respawnPos, currentPos)); ++ public static ServerPlayer.RespawnPosAngle of(Vec3 vec3d, BlockPos blockposition, boolean isBedSpawn, boolean isAnchorSpawn) { ++ return new ServerPlayer.RespawnPosAngle(vec3d, calculateLookAtYaw(vec3d, blockposition), isBedSpawn, isAnchorSpawn); ++ // CraftBukkit end + } + + private static float calculateLookAtYaw(Vec3 respawnPos, BlockPos currentPos) { + Vec3 vec3d1 = Vec3.atBottomCenterOf(currentPos).subtract(respawnPos).normalize(); + + return (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); ++ } ++ } ++ ++ // 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.level().getDayTime() + this.timeOffset; ++ } else { ++ // Adds timeOffset to the beginning of this day. ++ return this.level().getDayTime() - (this.level().getDayTime() % 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.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0)); ++ } else { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 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.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain)); ++ } ++ } else { ++ // Plugin ++ if (this.pluginRainPositionPrevious != this.pluginRainPosition) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.pluginRainPosition)); ++ } ++ } ++ ++ if (oldThunder != newThunder) { ++ if (this.weather == WeatherType.DOWNFALL || this.weather == null) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder)); ++ } else { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)); ++ } ++ } ++ } ++ ++ public void tickWeather() { ++ if (this.weather == null) return; ++ ++ this.pluginRainPositionPrevious = this.pluginRainPosition; ++ if (this.weather == WeatherType.DOWNFALL) { ++ this.pluginRainPosition += 0.01; ++ } else { ++ this.pluginRainPosition -= 0.01; ++ } ++ ++ this.pluginRainPosition = Mth.clamp(this.pluginRainPosition, 0.0F, 1.0F); ++ } ++ ++ public void resetPlayerWeather() { ++ this.weather = null; ++ this.setPlayerWeather(this.level().getLevelData().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false); ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")"; ++ } ++ ++ // SPIGOT-1903, MC-98153 ++ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) { ++ this.moveTo(x, y, z, yaw, pitch); ++ this.connection.resetPosition(); ++ } ++ ++ @Override ++ public boolean isImmobile() { ++ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs ++ } ++ ++ @Override ++ public Scoreboard getScoreboard() { ++ return this.getBukkitEntity().getScoreboard().getHandle(); ++ } ++ ++ public void reset() { ++ float exp = 0; ++ ++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) ++ exp = this.experienceProgress; ++ this.newTotalExp = this.totalExperience; ++ this.newLevel = this.experienceLevel; ++ } ++ ++ this.setHealth(this.getMaxHealth()); ++ this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset ++ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn ++ this.setRemainingFireTicks(0); ++ this.fallDistance = 0; ++ this.foodData = new FoodData(); ++ this.experienceLevel = this.newLevel; ++ this.totalExperience = this.newTotalExp; ++ this.experienceProgress = 0; ++ this.deathTime = 0; ++ this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent ++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); ++ this.effectsDirty = true; ++ this.containerMenu = this.inventoryMenu; ++ this.lastHurtByPlayer = null; ++ this.lastHurtByMob = null; ++ this.combatTracker = new CombatTracker(this); ++ this.lastSentExp = -1; ++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) ++ this.experienceProgress = exp; ++ } else { ++ this.giveExperiencePoints(this.newExp); ++ } ++ this.keepLevel = false; ++ this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death ++ this.skipDropExperience = false; // CraftBukkit - SPIGOT-7462: Reset experience drop skip, so that further deaths drop xp ++ } ++ ++ @Override ++ public CraftPlayer getBukkitEntity() { ++ return (CraftPlayer) super.getBukkitEntity(); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch new file mode 100644 index 0000000000..aba7ebeae1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch @@ -0,0 +1,463 @@ +--- a/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -13,6 +13,7 @@ + import net.minecraft.world.InteractionResult; + import net.minecraft.world.MenuProvider; + import net.minecraft.world.entity.EquipmentSlot; ++import net.minecraft.world.item.DoubleHighBlockItem; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.item.enchantment.EnchantmentHelper; +@@ -20,12 +21,30 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.GameMasterBlock; ++import net.minecraft.world.level.block.TrapDoorBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.ArrayList; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.CakeBlock; ++import net.minecraft.world.level.block.DoorBlock; ++import org.bukkit.GameMode; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockBreakEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.Event; ++import org.bukkit.event.block.Action; ++import org.bukkit.event.player.PlayerGameModeChangeEvent; ++import org.bukkit.event.player.PlayerInteractEvent; ++// CraftBukkit end ++ + public class ServerPlayerGameMode { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -42,6 +61,8 @@ + private BlockPos delayedDestroyPos; + private int delayedTickStart; + private int lastSentState; ++ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction ++ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction + + public ServerPlayerGameMode(ServerPlayer player) { + this.gameModeForPlayer = GameType.DEFAULT_MODE; +@@ -53,18 +74,32 @@ + } + + public boolean changeGameModeForPlayer(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event != null && event.isCancelled(); ++ } ++ @Nullable ++ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { ++ // Paper end - Expand PlayerGameModeChangeEvent + if (gameMode == this.gameModeForPlayer) { +- return false; ++ return null; // Paper - Expand PlayerGameModeChangeEvent + } else { +- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); ++ // CraftBukkit start ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return event; // Paper - Expand PlayerGameModeChangeEvent ++ } ++ // CraftBukkit end ++ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 + this.player.onUpdateAbilities(); +- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player)); ++ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit + this.level.updateSleepingPlayerList(); + if (gameMode == GameType.CREATIVE) { + this.player.resetCurrentImpulseContext(); + } + +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +@@ -92,12 +127,12 @@ + } + + public void tick() { +- ++this.gameTicks; ++ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit; + BlockState iblockdata; + + if (this.hasDelayedDestroy) { +- iblockdata = this.level.getBlockState(this.delayedDestroyPos); +- if (iblockdata.isAir()) { ++ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks ++ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks + this.hasDelayedDestroy = false; + } else { + float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); +@@ -108,7 +143,13 @@ + } + } + } else if (this.isDestroyingBlock) { +- iblockdata = this.level.getBlockState(this.destroyPos); ++ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead ++ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); ++ if (iblockdata == null) { ++ this.isDestroyingBlock = false; ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks + if (iblockdata.isAir()) { + this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); + this.lastSentState = -1; +@@ -137,6 +178,7 @@ + + public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { + if (!this.player.canInteractWithBlock(pos, 1.0D)) { ++ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away + this.debugLogging(pos, false, sequence, "too far"); + } else if (pos.getY() > worldHeight) { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); +@@ -146,16 +188,40 @@ + + if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) { + if (!this.level.mayInteract(this.player, pos)) { ++ // CraftBukkit start - fire PlayerInteractEvent ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); + this.debugLogging(pos, false, sequence, "may not interact"); ++ // Update any tile entity data for this block ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction ++ // CraftBukkit end + return; + } + ++ // CraftBukkit start ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ if (event.isCancelled()) { ++ // Let the client know the block still exists ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks ++ // Update any tile entity data for this block ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction ++ return; ++ } ++ // CraftBukkit end ++ + if (this.isCreative()) { + this.destroyAndAck(pos, sequence, "creative destroy"); + return; + } + ++ // Spigot start - handle debug stick left click for non-creative ++ if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) ++ && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block ++ return; ++ } ++ // Spigot end ++ + if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); + this.debugLogging(pos, false, sequence, "block action restricted"); +@@ -166,7 +232,21 @@ + float f = 1.0F; + + iblockdata = this.level.getBlockState(pos); +- if (!iblockdata.isAir()) { ++ // CraftBukkit start - Swings at air do *NOT* exist. ++ if (event.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. ++ // Paper start - Don't resync blocks ++ //BlockState data = this.level.getBlockState(pos); ++ //if (data.getBlock() instanceof DoorBlock) { ++ // // For some reason *BOTH* the bottom/top part have to be marked updated. ++ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); ++ //} else if (data.getBlock() instanceof TrapDoorBlock) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ //} ++ // Paper end - Don't resync blocks ++ } else if (!iblockdata.isAir()) { + EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> { + this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND); + }); +@@ -174,6 +254,26 @@ + f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos); + } + ++ if (event.useItemInHand() == Event.Result.DENY) { ++ // If we 'insta destroyed' then the client needs to be informed. ++ if (f > 1.0f) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks ++ } ++ return; ++ } ++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent ++ ++ if (blockEvent.isCancelled()) { ++ // Let the client know the block still exists ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block ++ return; ++ } ++ ++ if (blockEvent.getInstaBreak()) { ++ f = 2.0f; ++ } ++ // CraftBukkit end ++ + if (!iblockdata.isAir() && f >= 1.0F) { + this.destroyAndAck(pos, sequence, "insta mine"); + } else { +@@ -217,14 +317,18 @@ + this.debugLogging(pos, true, sequence, "stopped destroying"); + } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { + this.isDestroyingBlock = false; +- if (!Objects.equals(this.destroyPos, pos)) { +- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); +- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); +- this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper ++ ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled ++ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here ++ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); ++ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ this.destroyPos = BlockPos.ZERO; // Paper + } + + this.level.destroyBlockProgress(this.player.getId(), pos, -1); + this.debugLogging(pos, true, sequence, "aborted destroying"); ++ ++ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit + } + + } +@@ -242,19 +346,82 @@ + + public boolean destroyBlock(BlockPos pos) { + BlockState iblockdata = this.level.getBlockState(pos); ++ // CraftBukkit start - fire BlockBreakEvent ++ org.bukkit.block.Block bblock = CraftBlock.at(this.level, pos); ++ BlockBreakEvent event = null; + +- if (!this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { ++ if (this.player instanceof ServerPlayer) { ++ // Sword + Creative mode pre-cancel ++ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player); ++ ++ // Tell client the block is gone immediately then process events ++ // Don't tell the client if its a creative sword break because its not broken! ++ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block ++ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); ++ this.player.connection.send(packet); ++ } ++ ++ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity()); ++ ++ // Sword + Creative mode pre-cancel ++ event.setCancelled(isSwordNoBreak); ++ ++ // Calculate default block experience ++ BlockState nmsData = this.level.getBlockState(pos); ++ Block nmsBlock = nmsData.getBlock(); ++ ++ ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND); ++ ++ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) { ++ event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true)); ++ } ++ ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ if (isSwordNoBreak) { ++ return false; ++ } ++ // Paper start - Don't resync blocks ++ // Let the client know the block still exists ++ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ ++ // Brute force all possible updates ++ //for (Direction dir : Direction.values()) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); ++ //} ++ // Paper end - Don't resync blocks ++ ++ // Update any tile entity data for this block ++ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction ++ BlockEntity tileentity = this.level.getBlockEntity(pos); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } ++ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction ++ return false; ++ } ++ } ++ // CraftBukkit end ++ ++ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false + return false; + } else { ++ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins ++ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + +- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { ++ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + return false; + } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { + return false; + } else { ++ // CraftBukkit start ++ org.bukkit.block.BlockState state = bblock.getState(); ++ this.level.captureDrops = new ArrayList<>(); ++ // CraftBukkit end + BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player); + boolean flag = this.level.removeBlock(pos, false); + +@@ -262,20 +429,46 @@ + block.destroy(this.level, pos, iblockdata1); + } + ++ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place + if (this.isCreative()) { +- return true; ++ // return true; // CraftBukkit + } else { + ItemStack itemstack = this.player.getMainHandItem(); + ItemStack itemstack1 = itemstack.copy(); + boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); ++ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place + + itemstack.mineBlock(this.level, iblockdata1, pos, this.player); +- if (flag && flag1) { +- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1); ++ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion ++ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion + } + +- return true; ++ // return true; // CraftBukkit + } ++ // CraftBukkit start ++ java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world ++ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff ++ if (event.isDropItems()) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world ++ } ++ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up ++ ++ // Drop event experience ++ if (flag && event != null) { ++ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper ++ } ++ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) ++ if (mainHandStack != null) { ++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above ++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); ++ } ++ } ++ // Paper end - Trigger bee_nest_destroyed trigger in the correct place ++ ++ return true; ++ // CraftBukkit end + } + } + } +@@ -321,17 +514,63 @@ + } + } + ++ // CraftBukkit start - whole method ++ public boolean interactResult = false; ++ public boolean firedInteract = false; ++ public BlockPos interactPosition; ++ public InteractionHand interactHand; ++ public ItemStack interactItemStack; + public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { + BlockPos blockposition = hitResult.getBlockPos(); + BlockState iblockdata = world.getBlockState(blockposition); ++ boolean cancelledBlock = false; ++ boolean cancelledItem = false; // Paper - correctly handle items on cooldown + + if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { + return InteractionResult.FAIL; + } else if (this.gameModeForPlayer == GameType.SPECTATOR) { + MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); ++ cancelledBlock = !(itileinventory instanceof MenuProvider); ++ } + +- if (itileinventory != null) { +- player.openMenu(itileinventory); ++ if (player.getCooldowns().isOnCooldown(stack)) { ++ cancelledItem = true; // Paper - correctly handle items on cooldown ++ } ++ ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown ++ this.firedInteract = true; ++ this.interactResult = event.useItemInHand() == Event.Result.DENY; ++ this.interactPosition = blockposition.immutable(); ++ this.interactHand = hand; ++ this.interactItemStack = stack.copy(); ++ ++ if (event.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 (iblockdata.getBlock() instanceof DoorBlock) { ++ // Paper start - Don't resync blocks ++ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); ++ // Paper end - Don't resync blocks ++ } else if (iblockdata.getBlock() instanceof CakeBlock) { ++ player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake ++ } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { ++ // send a correcting update to the client, as it already placed the upper half of the bisected item ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks ++ ++ // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks ++ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method ++ } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); ++ } ++ // Paper end - extend Player Interact cancellation ++ player.getBukkitEntity().updateInventory(); // SPIGOT-2867 ++ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items ++ return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; ++ } else if (this.gameModeForPlayer == GameType.SPECTATOR) { ++ MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); ++ ++ if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + return InteractionResult.CONSUME; + } else { + return InteractionResult.PASS; +@@ -359,7 +598,7 @@ + } + } + +- if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) { ++ if (!stack.isEmpty() && !this.interactResult) { // add !interactResult SPIGOT-764 + UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult); + + if (this.isCreative()) { +@@ -377,6 +616,11 @@ + + return enuminteractionresult; + } else { ++ // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response ++ if (this.interactResult && this.interactResult != cancelledItem) { ++ this.player.resyncUsingItem(this.player); ++ } ++ // Paper end - Properly cancel usable items + return InteractionResult.PASS; + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch new file mode 100644 index 0000000000..ca9484bbeb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/server/level/TicketType.java ++++ b/net/minecraft/server/level/TicketType.java +@@ -7,6 +7,7 @@ + import net.minecraft.world.level.ChunkPos; + + public class TicketType { ++ public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper + + private final String name; + private final Comparator comparator; +@@ -22,6 +23,9 @@ + public static final TicketType PORTAL = TicketType.create("portal", Vec3i::compareTo, 300); + public static final TicketType ENDER_PEARL = TicketType.create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40); + public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); ++ public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit ++ public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit ++ public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type + + public static TicketType create(String name, Comparator argumentComparator) { + return new TicketType<>(name, argumentComparator, 0L); diff --git a/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch new file mode 100644 index 0000000000..415887c78f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch @@ -0,0 +1,105 @@ +--- a/net/minecraft/server/level/WorldGenRegion.java ++++ b/net/minecraft/server/level/WorldGenRegion.java +@@ -167,7 +167,27 @@ + int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ); + + return k < this.generatingStep.directDependencies().size(); ++ } ++ ++ // Paper start - if loaded util ++ @Nullable ++ @Override ++ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return this.getChunk(x, z, ChunkStatus.FULL, false); ++ } ++ ++ @Override ++ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getBlockState(blockposition); ++ } ++ ++ @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluidState(blockposition); + } ++ // Paper end + + @Override + public BlockState getBlockState(BlockPos pos) { +@@ -217,7 +237,8 @@ + if (iblockdata.isAir()) { + return false; + } else { +- if (drop) { ++ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens ++ if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation + BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; + + Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY); +@@ -264,6 +285,7 @@ + } + } + ++ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls + @Override + public boolean ensureCanWrite(BlockPos pos) { + int i = SectionPos.blockToSectionCoord(pos.getX()); +@@ -283,7 +305,15 @@ + + return true; + } else { ++ // Paper start - Buffer OOB setBlock calls ++ if (!hasSetFarWarned) { + Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); ++ hasSetFarWarned = true; ++ if (this.getServer() != null && this.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); ++ } ++ } ++ // Paper end - Buffer OOB setBlock calls + return false; + } + } +@@ -294,7 +324,7 @@ + return false; + } else { + ChunkAccess ichunkaccess = this.getChunk(pos); +- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); ++ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper + + if (iblockdata1 != null) { + this.level.onBlockStateChange(pos, iblockdata1, state); +@@ -310,6 +340,17 @@ + ichunkaccess.removeBlockEntity(pos); + } + } else { ++ // Paper start - Clear block entity before setting up a DUMMY block entity ++ // The concept of removing a block entity when the block itself changes is generally lifted ++ // from LevelChunk#setBlockState. ++ // It is however to note that this may only run if the block actually changes. ++ // Otherwise a chest block entity generated by a structure template that is later "updated" to ++ // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750) ++ // This logic is *also* found in LevelChunk#setBlockState. ++ if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) { ++ ichunkaccess.removeBlockEntity(pos); ++ } ++ // Paper end - Clear block entity before setting up a DUMMY block entity + CompoundTag nbttagcompound = new CompoundTag(); + + nbttagcompound.putInt("x", pos.getX()); +@@ -336,6 +377,13 @@ + + @Override + public boolean addFreshEntity(Entity entity) { ++ // CraftBukkit start ++ return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ @Override ++ public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ // CraftBukkit end + int i = SectionPos.blockToSectionCoord(entity.getBlockX()); + int j = SectionPos.blockToSectionCoord(entity.getBlockZ()); + diff --git a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch new file mode 100644 index 0000000000..a4f704f9e9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch @@ -0,0 +1,211 @@ +--- a/net/minecraft/server/network/LegacyQueryHandler.java ++++ b/net/minecraft/server/network/LegacyQueryHandler.java +@@ -15,6 +15,7 @@ + + private static final Logger LOGGER = LogUtils.getLogger(); + private final ServerInfo server; ++ private ByteBuf buf; // Paper + + public LegacyQueryHandler(ServerInfo server) { + this.server = server; +@@ -23,6 +24,16 @@ + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) { + ByteBuf bytebuf = (ByteBuf) object; + ++ // Paper start - Make legacy ping handler more reliable ++ if (this.buf != null) { ++ try { ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ } finally { ++ bytebuf.release(); ++ } ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); + boolean flag = true; + +@@ -34,11 +45,23 @@ + + SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress(); + int i = bytebuf.readableBytes(); +- String s; ++ String s = null; // Paper ++ // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper + + if (i == 0) { +- LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress); +- s = LegacyQueryHandler.createVersion0Response(this.server); ++ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: ""); // Paper - Respect logIPs option ++ ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 39, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ bytebuf.release(); ++ flag = false; ++ return; ++ } ++ s = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end + LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); + } else { + if (bytebuf.readUnsignedByte() != 1) { +@@ -46,16 +69,35 @@ + } + + if (bytebuf.isReadable()) { +- if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) { +- return; ++ // Paper start - Replace with improved version below ++ if (bytebuf.readUnsignedByte() != 250) { ++ s = this.readLegacy1_6(channelhandlercontext, bytebuf); ++ if (s == null) { ++ return; ++ } + } +- +- LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress); ++ // if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) { ++ // return; ++ // } ++ // ++ // LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress); ++ // Paper end + } else { +- LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress); ++ LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: ""); // Paper - Respect logIPs option + } + +- s = LegacyQueryHandler.createVersion1Response(this.server); ++ if (s == null) { ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 127, null); // Paper ++ if (event == null) { ++ channelhandlercontext.close(); ++ bytebuf.release(); ++ flag = false; ++ return; ++ } ++ s = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit ++ // Paper end ++ } + LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); + } + +@@ -106,14 +148,110 @@ + } + } + +- private static String createVersion0Response(ServerInfo server) { +- return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ // Paper start ++ private static String readLegacyString(ByteBuf buf) { ++ int size = buf.readShort() * Character.BYTES; ++ if (!buf.isReadable(size)) { ++ return null; ++ } ++ ++ String result = buf.toString(buf.readerIndex(), size, java.nio.charset.StandardCharsets.UTF_16BE); ++ buf.skipBytes(size); // toString doesn't increase readerIndex automatically ++ return result; + } + +- private static String createVersion1Response(ServerInfo server) { +- return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ private String readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) { ++ ByteBuf buf = this.buf; ++ ++ if (buf == null) { ++ this.buf = buf = ctx.alloc().buffer(); ++ buf.markReaderIndex(); ++ } else { ++ buf.resetReaderIndex(); ++ } ++ ++ buf.writeBytes(part); ++ ++ if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) { ++ return null; ++ } ++ ++ String s = readLegacyString(buf); ++ if (s == null) { ++ return null; ++ } ++ ++ if (!s.equals("MC|PingHost")) { ++ removeHandler(ctx); ++ return null; ++ } ++ ++ if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) { ++ return null; ++ } ++ ++ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer(); ++ int protocolVersion = buf.readByte(); ++ String host = readLegacyString(buf); ++ if (host == null) { ++ removeHandler(ctx); ++ return null; ++ } ++ int port = buf.readInt(); ++ ++ if (buf.isReadable()) { ++ removeHandler(ctx); ++ return null; ++ } ++ ++ buf.release(); ++ this.buf = null; ++ ++ LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): ""); // Paper - Respect logIPs option ++ ++ java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest( ++ server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ if (event == null) { ++ ctx.close(); ++ return null; ++ } ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ com.destroystokyo.paper.network.PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ return response; + } + ++ private void removeHandler(ChannelHandlerContext ctx) { ++ ByteBuf buf = this.buf; ++ this.buf = null; ++ ++ buf.resetReaderIndex(); ++ ctx.pipeline().remove(this); ++ ctx.fireChannelRead(buf); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) { ++ if (this.buf != null) { ++ this.buf.release(); ++ this.buf = null; ++ } ++ } ++ // Paper end ++ ++ // CraftBukkit start ++ private static String createVersion0Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) { ++ return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); ++ // CraftBukkit end ++ } ++ ++ // CraftBukkit start ++ private static String createVersion1Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) { ++ return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); ++ // CraftBukkit end ++ } ++ + private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buf) { + context.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch new file mode 100644 index 0000000000..e1ac133048 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/server/network/PlayerChunkSender.java ++++ b/net/minecraft/server/network/PlayerChunkSender.java +@@ -44,6 +44,11 @@ + public void dropChunk(ServerPlayer player, ChunkPos pos) { + if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) { + player.connection.send(new ClientboundForgetLevelChunkPacket(pos)); ++ // Paper start - PlayerChunkUnloadEvent ++ if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent(); ++ } ++ // Paper end - PlayerChunkUnloadEvent + } + } + +@@ -75,6 +80,11 @@ + + private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { + handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null)); ++ // Paper start - PlayerChunkLoadEvent ++ if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent(); ++ } ++ // Paper end - PlayerChunkLoadEvent + ChunkPos chunkPos = chunk.getPos(); + DebugPackets.sendPoiPacketsForChunk(world, chunkPos); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch new file mode 100644 index 0000000000..4ba7794d53 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -0,0 +1,418 @@ +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -4,11 +4,13 @@ + import com.mojang.logging.LogUtils; + import java.util.Objects; + import javax.annotation.Nullable; ++import net.minecraft.ChatFormatting; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; + import net.minecraft.ReportedException; + import net.minecraft.Util; + import net.minecraft.network.Connection; ++import net.minecraft.network.ConnectionProtocol; + import net.minecraft.network.DisconnectionDetails; + import net.minecraft.network.PacketSendListener; + import net.minecraft.network.chat.Component; +@@ -22,39 +24,88 @@ + import net.minecraft.network.protocol.common.ServerboundPongPacket; + import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; + import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; ++import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; ++import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ClientInformation; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.VisibleForDebug; + import net.minecraft.util.profiling.Profiler; + import net.minecraft.util.thread.BlockableEventLoop; + import org.slf4j.Logger; + +-public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener { ++// CraftBukkit start ++import io.netty.buffer.ByteBuf; ++import java.util.concurrent.ExecutionException; ++import net.minecraft.network.protocol.common.custom.DiscardedPayload; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.event.player.PlayerKickEvent; ++import org.bukkit.event.player.PlayerResourcePackStatusEvent; + ++public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection { ++ ++ @Override ++ public boolean isTransferred() { ++ return this.transferred; ++ } ++ ++ @Override ++ public ConnectionProtocol getProtocol() { ++ return this.protocol(); ++ } ++ ++ @Override ++ public void sendPacket(Packet packet) { ++ this.send(packet); ++ } ++ ++ @Override ++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes ++ this.disconnect(reason, cause); // Paper - kick event causes ++ } ++ // CraftBukkit end + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int LATENCY_CHECK_INTERVAL = 15000; + private static final int CLOSED_LISTENER_TIMEOUT = 15000; + private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout"); + static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); + protected final MinecraftServer server; +- protected final Connection connection; ++ public final Connection connection; // Paper + private final boolean transferred; +- private long keepAliveTime; ++ private long keepAliveTime = Util.getMillis(); // Paper + private boolean keepAlivePending; + private long keepAliveChallenge; + private long closedListenerTime; + private boolean closed = false; + private int latency; + private volatile boolean suspendFlushingOnServerThread = false; ++ public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks ++ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit ++ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support + +- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) { +- this.server = server; +- this.connection = connection; ++ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit ++ this.server = minecraftserver; ++ this.connection = networkmanager; + this.keepAliveTime = Util.getMillis(); +- this.latency = clientData.latency(); +- this.transferred = clientData.transferred(); ++ this.latency = commonlistenercookie.latency(); ++ this.transferred = commonlistenercookie.transferred(); ++ // CraftBukkit start - add fields and methods ++ this.player = player; ++ this.player.transferCookieConnection = this; ++ this.cserver = minecraftserver.server; + } ++ protected final ServerPlayer player; ++ protected final org.bukkit.craftbukkit.CraftServer cserver; ++ public boolean processedDisconnect; + ++ public CraftPlayer getCraftPlayer() { ++ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity(); ++ // CraftBukkit end ++ } ++ + private void close() { + if (!this.closed) { + this.closedListenerTime = Util.getMillis(); +@@ -65,6 +116,11 @@ + + @Override + public void onDisconnect(DisconnectionDetails info) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(info, null); ++ } ++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + if (this.isSingleplayerOwner()) { + ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); + this.server.halt(false); +@@ -80,13 +136,14 @@ + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { ++ //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async + if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { + int i = (int) (Util.getMillis() - this.keepAliveTime); + + this.latency = (this.latency * 3 + i) / 4; + this.keepAlivePending = false; + } else if (!this.isSingleplayerOwner()) { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnectAsync(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect + } + + } +@@ -94,38 +151,127 @@ + @Override + public void handlePong(ServerboundPongPacket packet) {} + ++ // CraftBukkit start ++ private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); ++ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); ++ + @Override +- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {} ++ public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { ++ // Paper start - Brand support ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { ++ this.player.clientBrandName = brandPayload.brand(); ++ } ++ // Paper end - Brand support ++ if (!(packet.payload() instanceof DiscardedPayload)) { ++ return; ++ } ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ ResourceLocation identifier = packet.payload().type().id(); ++ ByteBuf payload = ((DiscardedPayload)packet.payload()).data(); + ++ if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) { ++ try { ++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ this.getCraftPlayer().addChannel(channel); ++ } ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); ++ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ } ++ } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { ++ try { ++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ this.getCraftPlayer().removeChannel(channel); ++ } ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); ++ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ } ++ } else { ++ try { ++ byte[] data = new byte[payload.readableBytes()]; ++ payload.readBytes(data); ++ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... ++ if (identifier.equals(MINECRAFT_BRAND)) { ++ try { ++ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.player.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end - Brand support ++ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); ++ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ } ++ } ++ ++ } ++ ++ public final boolean isDisconnected() { ++ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs ++ } ++ // CraftBukkit end ++ + @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); + if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { + ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); +- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); ++ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause + } ++ // Paper start - adventure pack callbacks ++ // call the callbacks before the previously-existing event so the event has final say ++ final net.kyori.adventure.resource.ResourcePackCallback callback; ++ if (packet.action().isTerminal()) { ++ callback = this.packCallbacks.remove(packet.id()); ++ } else { ++ callback = this.packCallbacks.get(packet.id()); ++ } ++ if (callback != null) { ++ callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer()); ++ } ++ // Paper end ++ // Paper start - store last pack status ++ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; ++ player.getBukkitEntity().resourcePackStatus = packStatus; ++ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit ++ // Paper end - store last pack status + + } + + @Override + public void handleCookieResponse(ServerboundCookieResponsePacket packet) { +- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); ++ // CraftBukkit start ++ PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); ++ if (this.player.getBukkitEntity().handleCookieResponse(packet)) { ++ return; ++ } ++ // CraftBukkit end ++ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause + } + + protected void keepConnectionAlive() { + Profiler.get().push("keepAlive"); +- long i = Util.getMillis(); ++ // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings ++ // This should effectively place the keepalive handling back to "as it was" before 1.12.2 ++ long currentTime = Util.getMillis(); ++ long elapsedTime = currentTime - this.keepAliveTime; + +- if (!this.isSingleplayerOwner() && i - this.keepAliveTime >= 15000L) { +- if (this.keepAlivePending) { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); +- } else if (this.checkIfClosed(i)) { ++ if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets ++ if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause ++ } else if (this.checkIfClosed(currentTime)) { // Paper + this.keepAlivePending = true; +- this.keepAliveTime = i; +- this.keepAliveChallenge = i; ++ this.keepAliveTime = currentTime; ++ this.keepAliveChallenge = currentTime; + this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge)); + } + } ++ // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings + + Profiler.get().pop(); + } +@@ -133,7 +279,7 @@ + private boolean checkIfClosed(long time) { + if (this.closed) { + if (time - this.closedListenerTime >= 15000L) { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + + return false; +@@ -156,6 +302,14 @@ + } + + public void send(Packet packet, @Nullable PacketSendListener callbacks) { ++ // CraftBukkit start ++ if (packet == null || this.processedDisconnect) { // Spigot ++ return; ++ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) { ++ ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet; ++ this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld()); ++ } ++ // CraftBukkit end + if (packet.isTerminal()) { + this.close(); + } +@@ -175,22 +329,109 @@ + } + } + ++ // Paper start - adventure ++ public void disconnect(final net.kyori.adventure.text.Component reason) { ++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); ++ // Paper end - kick event causes ++ } ++ // Paper end - adventure ++ ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes + public void disconnect(Component reason) { +- this.disconnect(new DisconnectionDetails(reason)); ++ // Paper start - kick event causes ++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); + } ++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(new DisconnectionDetails(reason), cause); ++ // Paper end - kick event causes ++ } + +- public void disconnect(DisconnectionDetails disconnectionInfo) { +- this.connection.send(new ClientboundDisconnectPacket(disconnectionInfo.reason()), PacketSendListener.thenRun(() -> { +- this.connection.disconnect(disconnectionInfo); ++ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause ++ // CraftBukkit start - fire PlayerKickEvent ++ if (this.processedDisconnect) { ++ return; ++ } ++ if (!this.cserver.isPrimaryThread()) { ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes ++ return null; ++ } ++ }; ++ ++ this.server.processQueue.add(waitable); ++ ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); ++ } catch (ExecutionException e) { ++ throw new RuntimeException(e); ++ } ++ return; ++ } ++ ++ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure ++ ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes ++ ++ if (this.cserver.getServer().isRunning()) { ++ this.cserver.getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // Do not kick the player ++ return; ++ } ++ // Send the possibly modified leave message ++ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message ++ } ++ ++ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message ++ // CraftBukkit end ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason ++ this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { ++ this.connection.disconnect(disconnectiondetails); + })); ++ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message + this.connection.setReadOnly(); + MinecraftServer minecraftserver = this.server; + Connection networkmanager = this.connection; + + Objects.requireNonNull(this.connection); +- minecraftserver.executeBlocking(networkmanager::handleDisconnection); ++ // CraftBukkit - Don't wait ++ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper + } + ++ // Paper start - add proper async disconnect ++ public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); ++ } ++ ++ public void disconnectAsync(Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnectAsync(new DisconnectionDetails(reason), cause); ++ } ++ ++ public void disconnectAsync(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { ++ if (this.cserver.isPrimaryThread()) { ++ this.disconnect(disconnectionInfo, cause); ++ return; ++ } ++ this.connection.setReadOnly(); ++ this.server.scheduleOnMain(() -> { ++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); ++ if (ServerCommonPacketListenerImpl.this.player.quitReason == null) { ++ // cancelled ++ ServerCommonPacketListenerImpl.this.connection.enableAutoRead(); ++ } ++ }); ++ } ++ // Paper end - add proper async disconnect ++ + protected boolean isSingleplayerOwner() { + return this.server.isSingleplayerOwner(this.playerProfile()); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch new file mode 100644 index 0000000000..813b398d03 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -0,0 +1,89 @@ +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -38,6 +38,11 @@ + import net.minecraft.world.flag.FeatureFlags; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.CraftServerLinks; ++import org.bukkit.event.player.PlayerLinksSendEvent; ++// CraftBukkit end ++ + public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerConfigurationPacketListener, TickablePacketListener { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -50,10 +55,12 @@ + @Nullable + private SynchronizeRegistriesTask synchronizeRegistriesTask; + +- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) { +- super(server, connection, clientData); +- this.gameProfile = clientData.gameProfile(); +- this.clientInformation = clientData.clientInformation(); ++ // CraftBukkit start ++ public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { ++ super(minecraftserver, networkmanager, commonlistenercookie, player); ++ // CraftBukkit end ++ this.gameProfile = commonlistenercookie.gameProfile(); ++ this.clientInformation = commonlistenercookie.clientInformation(); + } + + @Override +@@ -63,6 +70,10 @@ + + @Override + public void onDisconnect(DisconnectionDetails info) { ++ // Paper start - Debugging ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { ++ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, info.reason().getString(), currentTask != null ? currentTask.type().id() : "null"); ++ } else // Paper end + ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, info.reason().getString()); + super.onDisconnect(info); + } +@@ -75,6 +86,12 @@ + public void startConfiguration() { + this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName()))); + ServerLinks serverlinks = this.server.serverLinks(); ++ // CraftBukkit start ++ CraftServerLinks wrapper = new CraftServerLinks(serverlinks); ++ PlayerLinksSendEvent event = new PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper); ++ this.player.getBukkitEntity().getServer().getPluginManager().callEvent(event); ++ serverlinks = wrapper.getServerLinks(); ++ // CraftBukkit end + + if (!serverlinks.isEmpty()) { + this.send(new ClientboundServerLinksPacket(serverlinks.untrust())); +@@ -107,6 +124,7 @@ + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + this.clientInformation = packet.information(); ++ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } + + @Override +@@ -143,18 +161,23 @@ + return; + } + +- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile); ++ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed + + if (ichatbasecomponent != null) { + this.disconnect(ichatbasecomponent); + return; + } + +- ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation); ++ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit + + playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation)); + } catch (Exception exception) { + ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception); ++ // Paper start - Debugging ++ if (MinecraftServer.getServer().isDebugging()) { ++ exception.printStackTrace(); ++ } ++ // Paper end - Debugging + this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA)); + this.connection.disconnect(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch new file mode 100644 index 0000000000..adc3cc15f4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch @@ -0,0 +1,156 @@ +--- a/net/minecraft/server/network/ServerConnectionListener.java ++++ b/net/minecraft/server/network/ServerConnectionListener.java +@@ -52,22 +52,36 @@ + + private static final Logger LOGGER = LogUtils.getLogger(); + public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + final MinecraftServer server; + public volatile boolean running; + private final List channels = Collections.synchronizedList(Lists.newArrayList()); + final List connections = Collections.synchronizedList(Lists.newArrayList()); ++ // Paper start - prevent blocking on adding a new connection while the server is ticking ++ private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private final void addPending() { ++ Connection connection; ++ while ((connection = pending.poll()) != null) { ++ connections.add(connection); ++ } ++ } ++ // Paper end - prevent blocking on adding a new connection while the server is ticking + + public ServerConnectionListener(MinecraftServer server) { + this.server = server; + this.running = true; + } + ++ // Paper start - Unix domain socket support + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ bind(new java.net.InetSocketAddress(address, port)); ++ } ++ public void bind(java.net.SocketAddress address) throws IOException { ++ // Paper end - Unix domain socket support + List list = this.channels; + + synchronized (this.channels) { +@@ -75,7 +89,13 @@ + EventLoopGroup eventloopgroup; + + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ // Paper start - Unix domain socket support ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + oclass = EpollServerSocketChannel.class; ++ } ++ // Paper end - Unix domain socket support + eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); + ServerConnectionListener.LOGGER.info("Using epoll channel type"); + } else { +@@ -84,6 +104,12 @@ + ServerConnectionListener.LOGGER.info("Using default channel type"); + } + ++ // Paper start - Warn people with console access that HAProxy is in use. ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled."); ++ } ++ // Paper end - Warn people with console access that HAProxy is in use. ++ + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) { + try { +@@ -100,16 +126,58 @@ + + Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null); + int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); +- Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); ++ Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error + +- ServerConnectionListener.this.connections.add(object); ++ //ServerConnectionListener.this.connections.add(object); // Paper ++ // Paper start - Add support for Proxy Protocol ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); ++ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { ++ @Override ++ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ++ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { ++ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { ++ String realaddress = message.sourceAddress(); ++ int realport = message.sourcePort(); ++ ++ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); ++ ++ Connection connection = (Connection) channel.pipeline().get("packet_handler"); ++ connection.address = socketaddr; ++ ++ // Paper start - Add API to get player's proxy address ++ final String proxyAddress = message.destinationAddress(); ++ final int proxyPort = message.destinationPort(); ++ ++ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort); ++ // Paper end - Add API to get player's proxy address ++ } ++ } else { ++ super.channelRead(ctx, msg); ++ } ++ } ++ }); ++ } ++ // Paper end - Add support for proxy protocol ++ pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking + ((Connection) object).configurePacketHandler(channelpipeline); + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); ++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners + } +- }).group(eventloopgroup).localAddress(address, port)).bind().syncUninterruptibly()); ++ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support + } + } + ++ // CraftBukkit start ++ public void acceptConnections() { ++ synchronized (this.channels) { ++ for (ChannelFuture future : this.channels) { ++ future.channel().config().setAutoRead(true); ++ } ++ } ++ } ++ // CraftBukkit end ++ + public SocketAddress startMemoryChannel() { + List list = this.channels; + ChannelFuture channelfuture; +@@ -153,6 +221,14 @@ + List list = this.connections; + + synchronized (this.connections) { ++ // Spigot Start ++ this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking ++ // 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.connections ); ++ } ++ // Spigot End + Iterator iterator = this.connections.iterator(); + + while (iterator.hasNext()) { +@@ -176,6 +252,10 @@ + networkmanager.setReadOnly(); + } + } 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.handleDisconnection(); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch new file mode 100644 index 0000000000..6c1745ae43 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -0,0 +1,2655 @@ +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -45,6 +45,7 @@ + import net.minecraft.network.Connection; + import net.minecraft.network.DisconnectionDetails; + import net.minecraft.network.TickablePacketListener; ++import net.minecraft.network.chat.ChatDecorator; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.LastSeenMessages; +@@ -65,12 +66,15 @@ + import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket; + import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; ++import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; + import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket; + import net.minecraft.network.protocol.game.ClientboundMoveVehiclePacket; + import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket; + import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; + import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; + import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket; + import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket; + import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; +@@ -148,14 +152,13 @@ + import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.HasCustomInventoryScreen; + import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.MoverType; + import net.minecraft.world.entity.PlayerRideableJumping; + import net.minecraft.world.entity.PositionMoveRotation; + import net.minecraft.world.entity.Relative; +-import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.ChatVisiblity; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.player.PlayerModelPart; + import net.minecraft.world.entity.player.ProfilePublicKey; + import net.minecraft.world.entity.projectile.AbstractArrow; +@@ -176,6 +179,7 @@ + import net.minecraft.world.item.crafting.RecipeHolder; + import net.minecraft.world.item.crafting.RecipeManager; + import net.minecraft.world.level.BaseCommandBlock; ++import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; + import net.minecraft.world.level.Level; +@@ -192,11 +196,72 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.NamespacedKey; + import org.slf4j.Logger; ++ ++// CraftBukkit start ++import io.papermc.paper.adventure.ChatProcessor; // Paper ++import io.papermc.paper.adventure.PaperAdventure; // Paper ++import com.mojang.datafixers.util.Pair; ++import java.util.Arrays; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Function; ++import net.minecraft.network.chat.OutgoingChatMessage; ++import net.minecraft.world.entity.animal.Bucketable; ++import net.minecraft.world.entity.animal.allay.Allay; ++import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.inventory.Slot; ++import net.minecraft.world.item.crafting.RecipeHolder; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftInput; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftItemType; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++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.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.SlotType; ++import org.bukkit.event.inventory.SmithItemEvent; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerAnimationEvent; ++import org.bukkit.event.player.PlayerAnimationType; ++import org.bukkit.event.player.PlayerChatEvent; ++import org.bukkit.event.player.PlayerCommandPreprocessEvent; ++import org.bukkit.event.player.PlayerInputEvent; ++import org.bukkit.event.player.PlayerInteractAtEntityEvent; ++import org.bukkit.event.player.PlayerInteractEntityEvent; ++import org.bukkit.event.player.PlayerItemHeldEvent; ++import org.bukkit.event.player.PlayerMoveEvent; ++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; ++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.inventory.SmithingInventory; ++// CraftBukkit end + + public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener { + +@@ -212,7 +277,9 @@ + private int tickCount; + private int ackBlockChangesUpTo = -1; + private final TickThrottler chatSpamThrottler = new TickThrottler(20, 200); ++ private final TickThrottler tabSpamThrottler = new TickThrottler(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement, io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit); // Paper - configurable tab spam limits + private final TickThrottler dropSpamThrottler = new TickThrottler(20, 1480); ++ private final TickThrottler recipeSpamPackets = new TickThrottler(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement, io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit); + private double firstGoodX; + private double firstGoodY; + private double firstGoodZ; +@@ -240,14 +307,16 @@ + private boolean receivedMovementThisTick; + @Nullable + private RemoteChatSession chatSession; ++ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + private SignedMessageChain.Decoder signedMessageDecoder; + private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { +- super(server, connection, clientData); ++ super(server, connection, clientData, player); // CraftBukkit + this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); + this.player = player; + player.connection = this; +@@ -256,9 +325,25 @@ + + Objects.requireNonNull(server); + this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, server::enforceSecureProfile); +- this.chatMessageChain = new FutureChain(server); ++ this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat + } + ++ // CraftBukkit start - add fields and methods ++ private int lastTick = MinecraftServer.currentTick; ++ private int allowedPlayerTicks = 1; ++ private int lastDropTick = MinecraftServer.currentTick; ++ private int lastBookTick = MinecraftServer.currentTick; ++ private int dropCount = 0; ++ ++ private boolean hasMoved = false; ++ 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; ++ // CraftBukkit end ++ + @Override + public void tick() { + if (this.ackBlockChangesUpTo > -1) { +@@ -277,7 +362,7 @@ + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { + if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying")); ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -296,7 +381,7 @@ + if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying")); ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -311,11 +396,21 @@ + + this.keepConnectionAlive(); + this.chatSpamThrottler.tick(); ++ this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits ++ this.recipeSpamPackets.tick(); // Paper - auto recipe limit + this.dropSpamThrottler.tick(); +- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); ++ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits ++ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + ++ // Paper start - Prevent causing expired keys from impacting new joins ++ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { ++ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); ++ hasLoggedExpiry = true; ++ } ++ // Paper end - Prevent causing expired keys from impacting new joins ++ + } + + private int getMaximumFlyingTicks(Entity vehicle) { +@@ -376,6 +471,12 @@ + @Override + public void handlePlayerInput(ServerboundPlayerInputPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (!packet.input().equals(this.player.getLastClientInput())) { ++ PlayerInputEvent event = new PlayerInputEvent(this.player.getBukkitEntity(), new CraftInput(packet.input())); ++ this.cserver.getPluginManager().callEvent(event); ++ } ++ // CraftBukkit end + this.player.setLastClientInput(packet.input()); + } + +@@ -395,27 +496,84 @@ + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) { + Entity entity = this.player.getRootVehicle(); ++ // Paper start - Don't allow vehicle movement from players while teleporting ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { ++ return; ++ } ++ // Paper end - Don't allow vehicle movement from players while teleporting + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.serverLevel(); +- double d0 = entity.getX(); +- double d1 = entity.getY(); +- double d2 = entity.getZ(); +- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x()); +- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y()); +- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z()); ++ // CraftBukkit - store current player position ++ double prevX = this.player.getX(); ++ double prevY = this.player.getY(); ++ double prevZ = this.player.getZ(); ++ float prevYaw = this.player.getYRot(); ++ float prevPitch = this.player.getXRot(); ++ // CraftBukkit end ++ double d0 = entity.getX(); final double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.getY(); final double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.getZ(); final double fromZ = d2; // Paper - OBFHELPER ++ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x()); final double toX = d3; // Paper - OBFHELPER ++ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y()); final double toY = d4; // Paper - OBFHELPER ++ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z()); final double toZ = d5; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.yRot()); + float f1 = Mth.wrapDegrees(packet.xRot()); + double d6 = d3 - this.vehicleFirstGoodX; + double d7 = d4 - this.vehicleFirstGoodY; + double d8 = d5 - this.vehicleFirstGoodZ; + double d9 = entity.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - fromX; ++ double currDeltaY = toY - fromY; ++ double currDeltaZ = toZ - fromZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d3 - this.vehicleLastGoodX; ++ double otherFieldY = d4 - this.vehicleLastGoodY; ++ double otherFieldZ = d5 - this.vehicleLastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + +- if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) { ++ // 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.receivedMovePacketCount; ++ int i = this.receivedMovePacketCount - this.knownMovePacketCount; ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { ++ ServerGamePacketListenerImpl.LOGGER.debug(this.player.getScoreboardName() + " is sending move packets too frequently (" + i + " packets since last tick)"); ++ i = 1; ++ } ++ ++ if (d10 > 0) { ++ this.allowedPlayerTicks -= 1; ++ } else { ++ this.allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (this.player.getAbilities().flying) { ++ speed = this.player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = this.player.getAbilities().walkingSpeed * 10f; ++ } ++ speed *= 2f; // TODO: Get the speed of the vehicle instead of the player ++ ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( ++ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || ++ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) ++ )) { ++ this.connection.send(ClientboundMoveVehiclePacket.fromEntity(entity)); ++ return; ++ } ++ // Paper end - Prevent moving into unloaded chunks ++ ++ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); + this.send(ClientboundMoveVehiclePacket.fromEntity(entity)); + return; +@@ -423,9 +581,9 @@ + + boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- d6 = d3 - this.vehicleLastGoodX; +- d7 = d4 - this.vehicleLastGoodY; +- d8 = d5 - this.vehicleLastGoodZ; ++ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d4 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = entity.verticalCollisionBelow; + + if (entity instanceof LivingEntity) { +@@ -449,19 +607,72 @@ + d10 = d6 * d6 + d7 * d7 + d8 * d8; + boolean flag2 = false; + +- if (d10 > 0.0625D) { ++ if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot + flag2 = true; + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); + } + + entity.absMoveTo(d3, d4, d5, f, f1); ++ this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + + if (flag && (flag2 || !flag3)) { + entity.absMoveTo(d0, d1, d2, f, f1); ++ this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.send(ClientboundMoveVehiclePacket.fromEntity(entity)); + return; ++ } ++ ++ // CraftBukkit start - fire PlayerMoveEvent ++ Player player = this.getCraftPlayer(); ++ if (!this.hasMoved) { ++ this.lastPosX = prevX; ++ this.lastPosY = prevY; ++ this.lastPosZ = prevZ; ++ this.lastYaw = prevYaw; ++ this.lastPitch = prevPitch; ++ this.hasMoved = true; ++ } ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); // Get the Players previous Event location. ++ Location to = CraftLocation.toBukkit(packet.position(), player.getWorld(), packet.yRot(), packet.xRot()); ++ ++ // 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.isImmobile()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ this.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.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } + } ++ // CraftBukkit end + + this.player.serverLevel().getChunkSource().move(this.player); + entity.recordMovementThroughBlocks(new Vec3(d0, d1, d2), entity.position()); +@@ -489,16 +700,17 @@ + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (packet.getId() == this.awaitingTeleport) { + if (this.awaitingPositionFromClient == null) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + return; + } + +- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); ++ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported + this.lastGoodX = this.awaitingPositionFromClient.x; + this.lastGoodY = this.awaitingPositionFromClient.y; + this.lastGoodZ = this.awaitingPositionFromClient.z; + this.player.hasChangedDimension(); + this.awaitingPositionFromClient = null; ++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit + } + + } +@@ -528,6 +740,7 @@ + @Override + public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ CraftEventFactory.callRecipeBookSettingsEvent(this.player, packet.getBookType(), packet.isOpen(), packet.isFiltering()); // CraftBukkit + this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering()); + } + +@@ -545,21 +758,104 @@ + + } + ++ // Paper start - AsyncTabCompleteEvent ++ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4, ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); ++ // Paper end - AsyncTabCompleteEvent + @Override + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { +- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async ++ // CraftBukkit start ++ if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits ++ this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause // Paper - add proper async disconnect ++ return; ++ } ++ // CraftBukkit end ++ // Paper start - Don't suggest if tab-complete is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ return; ++ } ++ // Paper end - Don't suggest if tab-complete is disabled ++ // Paper start ++ final int index; ++ if (packet.getCommand().length() > 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { ++ this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - add proper async disconnect ++ return; ++ } ++ // Paper end ++ // Paper start - AsyncTabCompleteEvent ++ TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); ++ } ++ ++ private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) { + StringReader stringreader = new StringReader(packet.getCommand()); + + if (stringreader.canRead() && stringreader.peek() == '/') { + stringreader.skip(); + } + ++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), true, null); ++ event.callEvent(); ++ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); ++ // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server ++ if (!event.isHandled()) { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ // This needs to be on main ++ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader)); ++ } else if (!completions.isEmpty()) { ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { ++ final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion()); ++ if (intSuggestion != null) { ++ builder.suggest(intSuggestion, PaperAdventure.asVanilla(completion.tooltip())); ++ } else { ++ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); ++ } ++ } ++ // Paper start - Brigadier API ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); ++ } ++ // Paper end - Brigadier API ++ } ++ } ++ // Paper start - brig API ++ private static Suggestions limitTo(final Suggestions suggestions, final int size) { ++ return suggestions.getList().size() <= size ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, size)); ++ } ++ // Paper end - brig API ++ ++ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { ++ // Paper end - AsyncTabCompleteEvent + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ // Paper start - Handle non-recoverable exceptions ++ if (!parseresults.getExceptions().isEmpty() ++ && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); ++ return; ++ } ++ // Paper end - Handle non-recoverable exceptions + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- Suggestions suggestions1 = suggestions.getList().size() <= 1000 ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000)); +- +- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1)); ++ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false ++ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { ++ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); ++ } ++ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false ++ // Paper start - Brigadier API ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); ++ } ++ // Paper end - Brigadier API + }); + } + +@@ -568,7 +864,7 @@ + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = null; +@@ -635,7 +931,7 @@ + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); +@@ -668,7 +964,7 @@ + ItemStack itemstack = iblockdata.getCloneItemStack(worldserver, blockposition, flag); + + if (!itemstack.isEmpty()) { +- if (flag) { ++ if (flag && this.player.getBukkitEntity().hasPermission("minecraft.nbt.copy")) { // Spigot + ServerGamePacketListenerImpl.addBlockDataToItem(iblockdata, worldserver, blockposition, itemstack); + } + +@@ -712,15 +1008,25 @@ + if (stack.isItemEnabled(this.player.level().enabledFeatures())) { + Inventory playerinventory = this.player.getInventory(); + int i = playerinventory.findSlotMatchingItem(stack); ++ // Paper start - Add PlayerPickItemEvent ++ final int sourceSlot = i; ++ final int targetSlot = Inventory.isHotbarSlot(sourceSlot) ? sourceSlot : playerinventory.getSuitableHotbarSlot(); ++ final Player bukkitPlayer = this.player.getBukkitEntity(); ++ final io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); ++ if (!event.callEvent()) { ++ return; ++ } ++ i = event.getSourceSlot(); + + if (i != -1) { +- if (Inventory.isHotbarSlot(i)) { +- playerinventory.selected = i; ++ if (Inventory.isHotbarSlot(i) && Inventory.isHotbarSlot(event.getTargetSlot())) { ++ playerinventory.selected = event.getTargetSlot(); + } else { +- playerinventory.pickSlot(i); ++ playerinventory.pickSlot(i, event.getTargetSlot()); + } + } else if (this.player.hasInfiniteMaterials()) { +- playerinventory.addAndPickItem(stack); ++ playerinventory.addAndPickItem(stack, event.getTargetSlot()); ++ // Paper end - Add PlayerPickItemEvent + } + + this.player.connection.send(new ClientboundSetHeldSlotPacket(playerinventory.selected)); +@@ -866,6 +1172,13 @@ + AbstractContainerMenu container = this.player.containerMenu; + + if (container instanceof MerchantMenu containermerchant) { ++ // CraftBukkit start ++ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant); ++ if (tradeSelectEvent.isCancelled()) { ++ this.player.getBukkitEntity().updateInventory(); ++ return; ++ } ++ // CraftBukkit end + if (!containermerchant.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containermerchant); + return; +@@ -879,6 +1192,51 @@ + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { ++ // Paper start - Book size limits ++ final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; ++ if (!this.cserver.isPrimaryThread() && pageMax.enabled()) { ++ final List pageList = packet.pages(); ++ long byteTotal = 0; ++ final int maxBookPageSize = pageMax.intValue(); ++ final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); ++ long byteAllowed = maxBookPageSize; ++ for (final String page : pageList) { ++ final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; ++ byteTotal += byteLength; ++ final int length = page.length(); ++ int multiByteCharacters = 0; ++ if (byteLength != length) { ++ // Count the number of multi byte characters ++ for (final char c : page.toCharArray()) { ++ if (c > 127) { ++ multiByteCharacters++; ++ } ++ } ++ } ++ ++ // Allow pages with fewer characters to consume less of the allowed byte quota ++ byteAllowed += maxBookPageSize * Math.clamp((double) length / 255D, 0.1D, 1) * multiplier; ++ ++ if (multiByteCharacters > 1) { ++ // Penalize multibyte characters ++ byteAllowed -= multiByteCharacters; ++ } ++ } ++ ++ if (byteTotal > byteAllowed) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); ++ this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect ++ return; ++ } ++ } ++ // Paper end - Book size limits ++ // CraftBukkit start ++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ this.disconnectAsync(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect ++ return; ++ } ++ this.lastBookTick = MinecraftServer.currentTick; ++ // CraftBukkit end + int i = packet.slot(); + + if (Inventory.isHotbarSlot(i) || i == 40) { +@@ -899,12 +1257,16 @@ + } + + private void updateBookContents(List pages, int slotId) { +- ItemStack itemstack = this.player.getInventory().getItem(slotId); ++ // CraftBukkit start ++ ItemStack handItem = this.player.getInventory().getItem(slotId); ++ ItemStack itemstack = handItem.copy(); ++ // CraftBukkit end + + if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { + List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); + + itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); ++ this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + } + +@@ -915,12 +1277,13 @@ + ItemStack itemstack1 = itemstack.transmuteCopy(Items.WRITTEN_BOOK); + + itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT); +- List> list1 = pages.stream().map((filteredtext1) -> { ++ List> list1 = (List>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error + return this.filterableFromOutgoing(filteredtext1).map(Component::literal); + }).toList(); + + itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true)); +- this.player.getInventory().setItem(slotId, itemstack1); ++ CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit ++ this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book + } + } + +@@ -978,26 +1341,34 @@ + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + ServerLevel worldserver = this.player.serverLevel(); + +- if (!this.player.wonGame) { ++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit + if (this.tickCount == 0) { + this.resetPosition(); + } + + if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) { +- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); +- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); +- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); +- float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); +- float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); ++ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER ++ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER ++ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER ++ float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER ++ float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER + + if (this.player.isPassenger()) { + this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); + this.player.serverLevel().getChunkSource().move(this.player); ++ this.allowedPlayerTicks = 20; // CraftBukkit + } else { ++ // CraftBukkit - Make sure the move is valid but then reset it for plugins to modify ++ double prevX = this.player.getX(); ++ double prevY = this.player.getY(); ++ double prevZ = this.player.getZ(); ++ float prevYaw = this.player.getYRot(); ++ float prevPitch = this.player.getXRot(); ++ // CraftBukkit end + double d3 = this.player.getX(); + double d4 = this.player.getY(); + double d5 = this.player.getZ(); +@@ -1005,7 +1376,16 @@ + double d7 = d1 - this.firstGoodY; + double d8 = d2 - this.firstGoodZ; + double d9 = this.player.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - prevX; ++ double currDeltaY = toY - prevY; ++ double currDeltaZ = toZ - prevZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d0 - this.lastGoodX; ++ double otherFieldY = d1 - this.lastGoodY; ++ double otherFieldZ = d2 - this.lastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d10 > 1.0D) { +@@ -1019,36 +1399,106 @@ + ++this.receivedMovePacketCount; + int i = this.receivedMovePacketCount - this.knownMovePacketCount; + +- 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)) { + ServerGamePacketListenerImpl.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); + i = 1; + } + ++ if (packet.hasRot || d10 > 0) { ++ this.allowedPlayerTicks -= 1; ++ } else { ++ this.allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (this.player.getAbilities().flying) { ++ speed = this.player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = this.player.getAbilities().walkingSpeed * 10f; ++ } ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (!event.isAllowed()) { ++ this.internalTeleport(PositionMoveRotation.of(this.player), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Add fail move event ++ } ++ // Paper end - Prevent moving into unloaded chunks ++ + if (this.shouldCheckPlayerMovement(flag)) { + float f2 = flag ? 300.0F : 100.0F; + +- if (d10 - d9 > (double) (f2 * (float) i)) { +- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); +- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +- return; ++ if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2))) { ++ // CraftBukkit end ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ if (event.getLogWarning()) ++ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); ++ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); ++ return; ++ } ++ // Paper end - Add fail move event + } + } + } + + AABB axisalignedbb = this.player.getBoundingBox(); + +- d6 = d0 - this.lastGoodX; +- d7 = d1 - this.lastGoodY; +- d8 = d2 - this.lastGoodZ; ++ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = d7 > 0.0D; + + if (this.player.onGround() && !packet.isOnGround() && flag1) { +- this.player.jumpFromGround(); ++ // Paper start - Add PlayerJumpEvent ++ Player player = this.getCraftPlayer(); ++ 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 (packet.hasPos) { ++ to.setX(packet.x); ++ to.setY(packet.y); ++ to.setZ(packet.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packet.hasRot) { ++ to.setYaw(packet.yRot); ++ to.setPitch(packet.xRot); ++ } ++ ++ com.destroystokyo.paper.event.player.PlayerJumpEvent event = new com.destroystokyo.paper.event.player.PlayerJumpEvent(player, from, to); ++ ++ if (event.callEvent()) { ++ this.player.jumpFromGround(); ++ } else { ++ from = event.getFrom(); ++ this.internalTeleport(new PositionMoveRotation(org.bukkit.craftbukkit.util.CraftLocation.toVec3D(from), Vec3.ZERO, from.getYaw(), from.getPitch()), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Add PlayerJumpEvent + } + + boolean flag2 = this.player.verticalCollisionBelow; + + this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); ++ this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.awaitingPositionFromClient != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d11 = d7; + + d6 = d0 - this.player.getX(); +@@ -1059,17 +1509,100 @@ + + d8 = d2 - this.player.getZ(); + d10 = d6 * d6 + d7 * d7 + d8 * d8; +- boolean flag3 = false; ++ boolean movedWrongly = false; // Paper - Add fail move event; rename + +- if (!this.player.isChangingDimension() && d10 > 0.0625D && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { +- flag3 = true; ++ if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ movedWrongly = true; ++ if (event.getLogWarning()) ++ // Paper end + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ } // Paper + } + +- if (!this.player.noPhysics && !this.player.isSleeping() && (flag3 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { +- this.teleport(d3, d4, d5, f, f1); ++ // Paper start - Add fail move event ++ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); ++ if (teleportBack) { ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (event.isAllowed()) { ++ teleportBack = false; ++ } ++ } ++ if (teleportBack) { ++ // Paper end - Add fail move event ++ this.internalTeleport(d3, d4, d5, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. + this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); + } else { ++ // CraftBukkit start - fire PlayerMoveEvent ++ // Reset to old location first ++ this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch); ++ ++ Player player = this.getCraftPlayer(); ++ if (!this.hasMoved) { ++ this.lastPosX = prevX; ++ this.lastPosY = prevY; ++ this.lastPosZ = prevZ; ++ this.lastYaw = prevYaw; ++ this.lastPitch = prevPitch; ++ this.hasMoved = true; ++ } ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.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 (packet.hasPos) { ++ to.setX(packet.x); ++ to.setY(packet.y); ++ to.setZ(packet.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packet.hasRot) { ++ to.setYaw(packet.yRot); ++ to.setPitch(packet.xRot); ++ } ++ ++ // 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.isImmobile()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ this.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.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ // CraftBukkit end + this.player.absMoveTo(d0, d1, d2, f, f1); + boolean flag4 = this.player.isAutoSpinAttack(); + +@@ -1119,6 +1652,7 @@ + this.awaitingTeleportTime = this.tickCount; + this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + } ++ this.allowedPlayerTicks = 20; // CraftBukkit + + return true; + } else { +@@ -1147,23 +1681,98 @@ + } + + public void teleport(double x, double y, double z, float yaw, float pitch) { +- this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet()); ++ // CraftBukkit start - Delegate to teleport(Location) ++ this.teleport(x, y, z, yaw, pitch, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + ++ public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { ++ return this.teleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet(), cause); ++ // CraftBukkit end ++ } ++ + public void teleport(PositionMoveRotation pos, Set flags) { ++ // CraftBukkit start ++ this.teleport(pos, flags, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public boolean teleport(PositionMoveRotation positionmoverotation, Set set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status ++ Player player = this.getCraftPlayer(); ++ Location from = player.getLocation(); ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this.player), positionmoverotation, set); ++ Location to = CraftLocation.toBukkit(absolutePosition.position(), this.getCraftPlayer().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ // SPIGOT-5171: Triggered on join ++ if (from.equals(to)) { ++ this.internalTeleport(positionmoverotation, set); ++ return true; // CraftBukkit - Return event status ++ } ++ ++ // Paper start - Teleport API ++ final Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); ++ for (final Relative relativeArgument : set) { ++ final io.papermc.paper.entity.TeleportFlag.Relative flag = org.bukkit.craftbukkit.entity.CraftPlayer.deltaRelativeToAPI(relativeArgument); ++ if (flag != null) relativeFlags.add(flag); ++ } ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags)); ++ // Paper end - Teleport API ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || !to.equals(event.getTo())) { ++ // set = Collections.emptySet(); // Can't relative teleport // Paper - Teleport API; Now you can! ++ to = event.isCancelled() ? event.getFrom() : event.getTo(); ++ positionmoverotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch()); ++ } ++ ++ this.internalTeleport(positionmoverotation, set); ++ return !event.isCancelled(); // CraftBukkit - Return event status ++ } ++ ++ public void teleport(Location dest) { ++ this.internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch()); ++ } ++ ++ private void internalTeleport(double d0, double d1, double d2, float f, float f1) { ++ this.internalTeleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet()); ++ } ++ ++ public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { ++ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper ++ // Paper start - Prevent teleporting dead entities ++ if (player.isRemoved()) { ++ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); ++ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); ++ return; ++ } ++ // Paper end - Prevent teleporting dead entities ++ if (Float.isNaN(positionmoverotation.yRot())) { ++ positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), 0, positionmoverotation.xRot()); ++ } ++ if (Float.isNaN(positionmoverotation.xRot())) { ++ positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), positionmoverotation.yRot(), 0); ++ } ++ ++ this.justTeleported = true; ++ // CraftBukkit end + this.awaitingTeleportTime = this.tickCount; + if (++this.awaitingTeleport == Integer.MAX_VALUE) { + this.awaitingTeleport = 0; + } + +- this.player.teleportSetPosition(pos, flags); ++ this.player.teleportSetPosition(positionmoverotation, set); + this.awaitingPositionFromClient = this.player.position(); +- this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, pos, flags)); ++ // CraftBukkit start - update last location ++ this.lastPosX = this.awaitingPositionFromClient.x; ++ this.lastPosY = this.awaitingPositionFromClient.y; ++ this.lastPosZ = this.awaitingPositionFromClient.z; ++ this.lastYaw = this.player.getYRot(); ++ this.lastPitch = this.player.getXRot(); ++ // CraftBukkit end ++ this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, positionmoverotation, set)); + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + if (this.player.hasClientLoaded()) { + BlockPos blockposition = packet.getPos(); + +@@ -1175,14 +1784,46 @@ + if (!this.player.isSpectator()) { + ItemStack itemstack = this.player.getItemInHand(InteractionHand.OFF_HAND); + +- this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND)); +- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack); ++ // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394) ++ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack); ++ CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(InteractionHand.MAIN_HAND)); ++ PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(this.getCraftPlayer(), mainHand.clone(), offHand.clone()); ++ this.cserver.getPluginManager().callEvent(swapItemsEvent); ++ if (swapItemsEvent.isCancelled()) { ++ return; ++ } ++ if (swapItemsEvent.getOffHandItem().equals(offHand)) { ++ this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND)); ++ } else { ++ this.player.setItemInHand(InteractionHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem())); ++ } ++ if (swapItemsEvent.getMainHandItem().equals(mainHand)) { ++ this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack); ++ } else { ++ this.player.setItemInHand(InteractionHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem())); ++ } ++ // CraftBukkit end + this.player.stopUsingItem(); + } + + return; + case DROP_ITEM: + 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) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); ++ this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause ++ return; ++ } ++ } ++ // CraftBukkit end + this.player.drop(false); + } + +@@ -1199,8 +1840,34 @@ + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: ++ // Paper start - Don't allow digging into unloaded chunks ++ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.capturedBlockEntity = false; ++ this.player.gameMode.captureSentBlockEntities = true; ++ // Paper end - Send block entities after destroy prediction + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.captureSentBlockEntities = false; ++ // If a block entity was modified speedup the block change ack to avoid the block entity ++ // being overriden. ++ if (this.player.gameMode.capturedBlockEntity) { ++ // manually tick ++ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); ++ this.player.connection.ackBlockChangesUpTo = -1; ++ ++ this.player.gameMode.capturedBlockEntity = false; ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } ++ } ++ // Paper end - Send block entities after destroy prediction + return; + default: + throw new IllegalArgumentException("Invalid player action"); +@@ -1215,12 +1882,34 @@ + Item item = stack.getItem(); + + return (item instanceof BlockItem || item instanceof BucketItem) && !player.getCooldowns().isOnCooldown(stack); ++ } ++ } ++ ++ // Spigot start - limit place/interactions ++ private int limitedPackets; ++ private long lastLimitedPacket = -1; ++ private static int getSpamThreshold() { return io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.incomingPacketThreshold; } // Paper - Configurable threshold ++ ++ private boolean checkLimit(long timestamp) { ++ if (this.lastLimitedPacket != -1 && timestamp - this.lastLimitedPacket < getSpamThreshold() && this.limitedPackets++ >= 8) { // Paper - Configurable threshold; raise packet limit to 8 ++ return false; ++ } ++ ++ if (this.lastLimitedPacket == -1 || timestamp - this.lastLimitedPacket >= getSpamThreshold()) { // Paper - Configurable threshold ++ this.lastLimitedPacket = timestamp; ++ this.limitedPackets = 0; ++ return true; + } ++ ++ return true; + } ++ // Spigot end + + @Override + public void handleUseItemOn(ServerboundUseItemOnPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit + if (this.player.hasClientLoaded()) { + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + ServerLevel worldserver = this.player.serverLevel(); +@@ -1230,6 +1919,11 @@ + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + BlockHitResult movingobjectpositionblock = packet.getHitResult(); + Vec3 vec3d = movingobjectpositionblock.getLocation(); ++ // Paper start - improve distance check ++ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { ++ return; ++ } ++ // Paper end - improve distance check + BlockPos blockposition = movingobjectpositionblock.getBlockPos(); + + if (this.player.canInteractWithBlock(blockposition, 1.0D)) { +@@ -1243,7 +1937,8 @@ + int i = this.player.level().getMaxY(); + + if (blockposition.getY() <= i) { +- if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) { ++ if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection ++ this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + + if (enuminteractionresult.consumesAction()) { +@@ -1257,11 +1952,11 @@ + } else if (enuminteractionresult instanceof InteractionResult.Success) { + InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; + +- if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) { ++ if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { // Paper - Call interact event + this.player.swing(enumhand, true); + } + } +- } ++ } else { this.player.containerMenu.sendAllDataToRemote(); } // Paper - Fix inventory desync; MC-99075 + } else { + MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i).withStyle(ChatFormatting.RED); + +@@ -1281,6 +1976,8 @@ + @Override + public void handleUseItem(ServerboundUseItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit + if (this.player.hasClientLoaded()) { + this.ackBlockChangesUpTo(packet.getSequence()); + ServerLevel worldserver = this.player.serverLevel(); +@@ -1296,6 +1993,48 @@ + this.player.absRotateTo(f, f1); + } + ++ // CraftBukkit start ++ // Raytrace to look for 'rogue armswings' ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Vec3 vec3d = new Vec3(d0, d1, d2); ++ ++ float f3 = Mth.cos(-f * 0.017453292F - 3.1415927F); ++ float f4 = Mth.sin(-f * 0.017453292F - 3.1415927F); ++ float f5 = -Mth.cos(-f1 * 0.017453292F); ++ float f6 = Mth.sin(-f1 * 0.017453292F); ++ float f7 = f4 * f5; ++ float f8 = f3 * f5; ++ double d3 = this.player.blockInteractionRange(); ++ Vec3 vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); ++ HitResult movingobjectposition = this.player.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player)); ++ ++ boolean cancelled; ++ if (movingobjectposition == null || movingobjectposition.getType() != HitResult.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 { ++ BlockHitResult movingobjectpositionblock = (BlockHitResult) movingobjectposition; ++ if (this.player.gameMode.firedInteract && this.player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && this.player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameComponents(this.player.gameMode.interactItemStack, itemstack)) { ++ cancelled = this.player.gameMode.interactResult; ++ } else { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation()); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } ++ this.player.gameMode.firedInteract = false; ++ } ++ ++ if (cancelled) { ++ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items ++ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 ++ return; ++ } ++ itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event ++ if (itemstack.isEmpty()) { ++ return; ++ } ++ // CraftBukkit end + InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); + + if (enuminteractionresult instanceof InteractionResult.Success) { +@@ -1321,7 +2060,7 @@ + Entity entity = packet.getEntity(worldserver); + + if (entity != null) { +- this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true); ++ this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit + return; + } + } +@@ -1342,22 +2081,52 @@ + + @Override + public void onDisconnect(DisconnectionDetails info) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(info, null); ++ } ++ @Override ++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent ++ // CraftBukkit start - Rarely it would send a disconnect line twice ++ if (this.processedDisconnect) { ++ return; ++ } else { ++ this.processedDisconnect = true; ++ } ++ // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString()); +- this.removePlayerFromWorld(); +- super.onDisconnect(info); ++ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent ++ super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent + } + ++ // Paper start - Fix kick event leave message not being sent + private void removePlayerFromWorld() { ++ this.removePlayerFromWorld(null); ++ } ++ ++ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + this.chatMessageChain.close(); ++ // CraftBukkit start - Replace vanilla quit message handling with our own. ++ /* + this.server.invalidateStatus(); +- this.server.getPlayerList().broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false); ++ this.server.getPlayerList().broadcastSystemMessage(IChatBaseComponent.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(EnumChatFormat.YELLOW), false); ++ */ ++ + this.player.disconnect(); +- this.server.getPlayerList().remove(this.player); ++ // Paper start - Adventure ++ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used ++ if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); ++ // Paper end ++ } ++ // CraftBukkit end + this.player.getTextFilter().leave(); + } + + public void ackBlockChangesUpTo(int sequence) { + if (sequence < 0) { ++ this.disconnect(Component.literal("Expected packet sequence nr >= 0"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - Treat sequence violations like they should be + throw new IllegalArgumentException("Expected packet sequence nr >= 0"); + } else { + this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); +@@ -1367,7 +2136,17 @@ + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change ++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.send(new ClientboundSetHeldSlotPacket(this.player.getInventory().selected)); ++ this.player.resetLastActionTime(); ++ return; ++ } ++ // CraftBukkit end + if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) { + this.player.stopUsingItem(); + } +@@ -1376,11 +2155,18 @@ + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + + @Override + public void handleChat(ServerboundChatPacket packet) { ++ // CraftBukkit start - async chat ++ // SPIGOT-3638 ++ if (this.server.isStopped()) { ++ return; ++ } ++ // CraftBukkit end + Optional optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages()); + + if (!optional.isEmpty()) { +@@ -1394,27 +2180,46 @@ + return; + } + +- CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()); +- Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); ++ CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat ++ CompletableFuture componentFuture = this.server.getChatDecorator().decorate(this.player, null, playerchatmessage.decoratedContent()); // Paper - Adventure + +- this.chatMessageChain.append(completablefuture, (filteredtext) -> { +- PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(ichatbasecomponent).filter(filteredtext.mask()); ++ this.chatMessageChain.append(CompletableFuture.allOf(completablefuture, componentFuture), (filteredtext) -> { // Paper - Adventure ++ PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(componentFuture.join()).filter(completablefuture.join().mask()); // Paper - Adventure + + this.broadcastChatMessage(playerchatmessage1); + }); +- }); ++ }, false); // CraftBukkit - async chat + } + } + + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + this.tryHandleChat(packet.command(), () -> { ++ // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands ++ if (this.player.hasDisconnected()) { ++ return; ++ } ++ // CraftBukkit end + this.performUnsignedChatCommand(packet.command()); +- this.detectRateSpam(); +- }); ++ this.detectRateSpam("/" + packet.command()); // Spigot ++ }, true); // CraftBukkit - sync commands + } + + private void performUnsignedChatCommand(String command) { ++ // CraftBukkit start ++ String command1 = "/" + command; ++ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check ++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1); ++ } ++ ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server)); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ command = event.getMessage().substring(1); ++ // CraftBukkit end + ParseResults parseresults = this.parseCommand(command); + + if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseresults)) { +@@ -1431,30 +2236,58 @@ + + if (!optional.isEmpty()) { + this.tryHandleChat(packet.command(), () -> { ++ // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands ++ if (this.player.hasDisconnected()) { ++ return; ++ } ++ // CraftBukkit end + this.performSignedChatCommand(packet, (LastSeenMessages) optional.get()); +- this.detectRateSpam(); +- }); ++ this.detectRateSpam("/" + packet.command()); // Spigot ++ }, true); // CraftBukkit - sync commands + } + } + + private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) { +- ParseResults parseresults = this.parseCommand(packet.command()); ++ // CraftBukkit start ++ String command = "/" + packet.command(); ++ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check ++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); ++ } // Paper - Add missing SpigotConfig logCommands check + +- Map map; ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); ++ this.cserver.getPluginManager().callEvent(event); ++ command = event.getMessage().substring(1); + ++ // Paper start - Fix cancellation and message changing ++ ParseResults parseresults = this.parseCommand(packet.command()); ++ ++ Map map; + try { ++ // Always parse the original command to add to the chat chain + map = this.collectSignedArguments(packet, SignableCommand.of(parseresults), lastSeenMessages); + } catch (SignedMessageChain.DecodeException signedmessagechain_a) { + this.handleMessageDecodeFailure(signedmessagechain_a); + return; + } + ++ if (event.isCancelled()) { ++ // Only now are we actually good to return ++ return; ++ } ++ ++ // Remove signed parts if the command was changed ++ if (!command.equals(packet.command())) { ++ parseresults = this.parseCommand(command); ++ map = Collections.emptyMap(); ++ } ++ // Paper end - Fix cancellation and message changing ++ + CommandSigningContext.SignedArguments commandsigningcontext_a = new CommandSigningContext.SignedArguments(map); + +- parseresults = Commands.mapSource(parseresults, (commandlistenerwrapper) -> { ++ parseresults = Commands.mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error + return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain); + }); +- this.server.getCommands().performCommand(parseresults, packet.command()); ++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit + } + + private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) { +@@ -1530,14 +2363,20 @@ + return com_mojang_brigadier_commanddispatcher.parse(command, this.player.createCommandSourceStack()); + } + +- private void tryHandleChat(String message, Runnable callback) { +- if (ServerGamePacketListenerImpl.isChatMessageIllegal(message)) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters")); +- } else if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { ++ private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit ++ if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) { ++ this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper // Paper - add proper async disconnect ++ } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); + } else { + this.player.resetLastActionTime(); +- this.server.execute(callback); ++ // CraftBukkit start ++ if (sync) { ++ this.server.execute(runnable); ++ } else { ++ runnable.run(); ++ } ++ // CraftBukkit end + } + } + +@@ -1549,7 +2388,7 @@ + + if (optional.isEmpty()) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect + } + + return optional; +@@ -1566,6 +2405,117 @@ + return false; + } + ++ // CraftBukkit start - add method ++ public void chat(String s, PlayerChatMessage original, boolean async) { ++ if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { ++ return; ++ } ++ OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); ++ ++ if (false && !async && s.startsWith("/")) { // Paper - Don't handle commands in chat logic ++ this.handleCommand(s); ++ } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { ++ // Do nothing, this is coming from a plugin ++ // Paper start ++ } else if (true) { ++ if (!async && !org.bukkit.Bukkit.isPrimaryThread()) { ++ org.spigotmc.AsyncCatcher.catchOp("Asynchronous player chat is not allowed here"); ++ } ++ final ChatProcessor cp = new ChatProcessor(this.server, this.player, original, async); ++ cp.process(); ++ // Paper end ++ } else if (false) { // Paper ++ Player player = this.getCraftPlayer(); ++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(this.server)); ++ String originalFormat = event.getFormat(), originalMessage = event.getMessage(); ++ this.cserver.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()); ++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) { ++ if (!org.spigotmc.SpigotConfig.bungee && originalFormat.equals(queueEvent.getFormat()) && originalMessage.equals(queueEvent.getMessage()) && queueEvent.getPlayer().getName().equalsIgnoreCase(queueEvent.getPlayer().getDisplayName())) { // Spigot ++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player)); ++ return null; ++ } ++ ++ for (ServerPlayer recipient : ServerGamePacketListenerImpl.this.server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); ++ } ++ } else { ++ for (Player player : queueEvent.getRecipients()) { ++ player.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); ++ } ++ } ++ ServerGamePacketListenerImpl.this.server.console.sendMessage(message); ++ ++ return null; ++ }}; ++ if (async) { ++ this.server.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()); ++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) { ++ if (!org.spigotmc.SpigotConfig.bungee && originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) { // Spigot ++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player)); ++ return; ++ } ++ ++ for (ServerPlayer recipient : this.server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ } ++ } else { ++ for (Player recipient : event.getRecipients()) { ++ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ } ++ } ++ this.server.console.sendMessage(s); ++ } ++ } ++ } ++ ++ @Deprecated // Paper ++ public void handleCommand(String s) { // Paper - private -> public ++ // Paper start - Remove all this old duplicated logic ++ if (s.startsWith("/")) { ++ s = s.substring(1); ++ } ++ /* ++ It should be noted that this represents the "legacy" command execution path. ++ Api can call commands even if there is no additional context provided. ++ This method should ONLY be used if you need to execute a command WITHOUT ++ an actual player's input. ++ */ ++ this.performUnsignedChatCommand(s); ++ // Paper end ++ } ++ // CraftBukkit end ++ + private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException { + SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages); + +@@ -1573,15 +2523,44 @@ + } + + private void broadcastChatMessage(PlayerChatMessage message) { +- this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, (Entity) this.player)); +- this.detectRateSpam(); ++ // CraftBukkit start ++ String s = message.signedContent(); ++ if (s.isEmpty()) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message"); ++ } else if (this.getCraftPlayer().isConversing()) { ++ final String conversationInput = s; ++ this.server.processQueue.add(new Runnable() { ++ @Override ++ public void run() { ++ ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput); ++ } ++ }); ++ } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check ++ this.send(new ClientboundSystemChatPacket(Component.translatable("chat.cannotSend").withStyle(ChatFormatting.RED), false)); ++ } else { ++ this.chat(s, message, true); ++ } ++ // this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player)); ++ // CraftBukkit end ++ this.detectRateSpam(s); // Spigot + } + +- private void detectRateSpam() { +- this.chatSpamThrottler.increment(); +- if (!this.chatSpamThrottler.isUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { +- this.disconnect((Component) Component.translatable("disconnect.spam")); ++ // Spigot start - spam exclusions ++ private void detectRateSpam(String s) { ++ // CraftBukkit start - replaced with thread safe throttle ++ for ( String exclude : org.spigotmc.SpigotConfig.spamExclusions ) ++ { ++ if ( exclude != null && s.startsWith( exclude ) ) ++ { ++ return; ++ } + } ++ // Spigot end ++ // this.chatSpamThrottler.increment(); ++ if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { ++ // CraftBukkit end ++ this.disconnectAsync((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Paper - add proper async disconnect ++ } + + } + +@@ -1592,7 +2571,7 @@ + synchronized (this.lastSeenMessages) { + if (!this.lastSeenMessages.applyOffset(packet.offset())) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect + } + + } +@@ -1601,7 +2580,40 @@ + @Override + public void handleAnimate(ServerboundSwingPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); ++ // CraftBukkit start - Raytrace to look for 'rogue armswings' ++ float f1 = this.player.getXRot(); ++ float f2 = this.player.getYRot(); ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Location origin = new Location(this.player.level().getWorld(), d0, d1, d2, f2, f1); ++ ++ double d3 = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange()); ++ // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time ++ // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities ++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 ++ Entity handle = ((CraftEntity) entity).getHandle(); ++ return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); ++ }); ++ if (result == null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } else { // Paper start - Call interact event ++ GameType gameType = this.player.gameMode.getGameModeForPlayer(); ++ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } ++ } // Paper end - Call interact event ++ ++ // Arm swing animation ++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end + this.player.swing(packet.getHand()); + } + +@@ -1609,6 +2621,29 @@ + public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasClientLoaded()) { ++ // CraftBukkit start ++ if (this.player.isRemoved()) return; ++ switch (packet.getAction()) { ++ case PRESS_SHIFT_KEY: ++ case RELEASE_SHIFT_KEY: ++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.PRESS_SHIFT_KEY); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ break; ++ case START_SPRINTING: ++ case STOP_SPRINTING: ++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.START_SPRINTING); ++ this.cserver.getPluginManager().callEvent(e2); ++ ++ if (e2.isCancelled()) { ++ return; ++ } ++ break; ++ } ++ // CraftBukkit end + this.player.resetLastActionTime(); + Entity entity; + PlayerRideableJumping ijumpable; +@@ -1616,6 +2651,11 @@ + switch (packet.getAction()) { + case PRESS_SHIFT_KEY: + this.player.setShiftKeyDown(true); ++ // Paper start - Add option to make parrots stay ++ if (this.player.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) { ++ this.player.removeEntitiesOnShoulder(); ++ } ++ // Paper end - Add option to make parrots stay + break; + case RELEASE_SHIFT_KEY: + this.player.setShiftKeyDown(false); +@@ -1684,15 +2724,25 @@ + } + + if (i > 4096) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats")); ++ this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause // Paper - add proper async disconnect + } + + } + } + + public void sendPlayerChatMessage(PlayerChatMessage message, ChatType.Bound params) { ++ // CraftBukkit start - SPIGOT-7262: if hidden we have to send as disguised message. Query whether we should send at all (but changing this may not be expected). ++ if (!this.getCraftPlayer().canSeePlayer(message.link().sender())) { ++ this.sendDisguisedChatMessage(message.decoratedContent(), params); ++ return; ++ } ++ // CraftBukkit end ++ // Paper start - Ensure that client receives chat packets in the same order that we add into the message signature cache ++ synchronized (this.messageSignatureCache) { + this.send(new ClientboundPlayerChatPacket(message.link().sender(), message.link().index(), message.signature(), message.signedBody().pack(this.messageSignatureCache), message.unsignedContent(), message.filterMask(), params)); + this.addPendingMessage(message); ++ } ++ // Paper end - Ensure that client receives chat packets in the same order that we add into the message signature cache + } + + public void sendDisguisedChatMessage(Component message, ChatType.Bound params) { +@@ -1703,6 +2753,18 @@ + return this.connection.getRemoteAddress(); + } + ++ // Spigot Start ++ public SocketAddress getRawAddress() ++ { ++ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something ++ if (connection.channel.remoteAddress() == null) { ++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); ++ } ++ // Paper end - Unix domain socket support ++ return this.connection.channel.remoteAddress(); ++ } ++ // Spigot End ++ + public void switchToConfig() { + this.waitingForSwitchToConfig = true; + this.removePlayerFromWorld(); +@@ -1718,9 +2780,17 @@ + @Override + public void handleInteract(ServerboundInteractPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + if (this.player.hasClientLoaded()) { + final ServerLevel worldserver = this.player.serverLevel(); + final Entity entity = packet.getTarget(worldserver); ++ // Spigot Start ++ if ( entity == this.player && !this.player.isSpectator() ) ++ { ++ this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause ++ return; ++ } ++ // Spigot End + + this.player.resetLastActionTime(); + this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); +@@ -1731,22 +2801,61 @@ + + AABB axisalignedbb = entity.getBoundingBox(); + +- if (this.player.canInteractWithEntity(axisalignedbb, 3.0D)) { ++ if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range + packet.dispatch(new ServerboundInteractPacket.Handler() { +- private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction action) { +- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(hand); ++ private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit ++ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); + + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + ItemStack itemstack1 = itemstack.copy(); +- InteractionResult enuminteractionresult = action.run(ServerGamePacketListenerImpl.this.player, entity, hand); ++ // CraftBukkit start ++ ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); ++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof Mob; ++ Item origItem = ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null ? null : ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem(); + ++ ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); ++ ++ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a ++ if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { ++ entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); ++ } ++ ++ if (triggerLeashUpdate && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { ++ // Refresh the current leash state ++ ServerGamePacketListenerImpl.this.send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder())); ++ } ++ ++ if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) { ++ // Refresh the current entity metadata ++ entity.refreshEntityData(ServerGamePacketListenerImpl.this.player); ++ // SPIGOT-7136 - Allays ++ if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync ++ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize ++ } ++ ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory ++ } ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ InteractionResult enuminteractionresult = playerconnection_a.run(ServerGamePacketListenerImpl.this.player, entity, enumhand); ++ ++ // CraftBukkit start ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end ++ + if (enuminteractionresult instanceof InteractionResult.Success) { + InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; + ItemStack itemstack2 = enuminteractionresult_d.wasItemInteraction() ? itemstack1 : ItemStack.EMPTY; + + CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemstack2, entity); + if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) { +- ServerGamePacketListenerImpl.this.player.swing(hand, true); ++ ServerGamePacketListenerImpl.this.player.swing(enumhand, true); + } + } + +@@ -1755,19 +2864,20 @@ + + @Override + public void onInteraction(InteractionHand hand) { +- this.performInteraction(hand, Player::interactOn); ++ this.performInteraction(hand, net.minecraft.world.entity.player.Player::interactOn, new PlayerInteractEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override + public void onInteraction(InteractionHand hand, Vec3 pos) { + this.performInteraction(hand, (entityplayer, entity1, enumhand1) -> { + return entity1.interactAt(entityplayer, pos, enumhand1); +- }); ++ }, new PlayerInteractAtEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(pos.x, pos.y, pos.z), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override + public void onAttack() { +- if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && entity != ServerGamePacketListenerImpl.this.player) { ++ // CraftBukkit ++ if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && (entity != ServerGamePacketListenerImpl.this.player || ServerGamePacketListenerImpl.this.player.isSpectator())) { + label23: + { + if (entity instanceof AbstractArrow) { +@@ -1785,17 +2895,41 @@ + } + + ServerGamePacketListenerImpl.this.player.attack(entity); ++ // CraftBukkit start ++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) { ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end + return; + } + } + +- ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); ++ ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause + ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); + } + }); + } + } ++ // Paper start - PlayerUseUnknownEntityEvent ++ else { ++ packet.dispatch(new net.minecraft.network.protocol.game.ServerboundInteractPacket.Handler() { ++ @Override ++ public void onInteraction(net.minecraft.world.InteractionHand hand) { ++ CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, null); ++ } + ++ @Override ++ public void onInteraction(net.minecraft.world.InteractionHand hand, net.minecraft.world.phys.Vec3 pos) { ++ CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, pos); ++ } ++ ++ @Override ++ public void onAttack() { ++ CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, net.minecraft.world.InteractionHand.MAIN_HAND, null); ++ } ++ }); ++ } ++ // Paper end - PlayerUseUnknownEntityEvent + } + } + +@@ -1809,7 +2943,7 @@ + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true, Entity.RemovalReason.CHANGED_DIMENSION); ++ this.player = this.server.getPlayerList().respawn(this.player, true, Entity.RemovalReason.CHANGED_DIMENSION, RespawnReason.END_PORTAL); // CraftBukkit + this.resetPosition(); + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); + } else { +@@ -1817,11 +2951,11 @@ + return; + } + +- this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED); ++ this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit + this.resetPosition(); + if (this.server.isHardcore()) { +- this.player.setGameMode(GameType.SPECTATOR); +- ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); ++ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent ++ ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world + } + } + break; +@@ -1833,16 +2967,27 @@ + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { ++ // Paper start - Inventory close reason ++ this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); ++ } ++ public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end - Inventory close reason + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ ++ if (this.player.isImmobile()) return; // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper ++ + this.player.doCloseContainer(); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- if (this.player.containerMenu.containerId == packet.getContainerId()) { +- if (this.player.isSpectator()) { ++ if (this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu.stillValid(this.player)) { // CraftBukkit ++ boolean cancelled = this.player.isSpectator(); // CraftBukkit - see below if ++ if (false/*this.player.isSpectator()*/) { // CraftBukkit + this.player.containerMenu.sendAllDataToRemote(); + } else if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); +@@ -1855,7 +3000,315 @@ + boolean flag = packet.getStateId() != this.player.containerMenu.getStateId(); + + this.player.containerMenu.suppressRemoteUpdates(); +- this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player); ++ // CraftBukkit start - Call InventoryClickEvent ++ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) { ++ return; ++ } ++ ++ InventoryView inventory = this.player.containerMenu.getBukkitView(); ++ SlotType type = inventory.getSlotType(packet.getSlotNum()); ++ ++ InventoryClickEvent event; ++ ClickType click = ClickType.UNKNOWN; ++ InventoryAction action = InventoryAction.UNKNOWN; ++ ++ ItemStack itemstack = ItemStack.EMPTY; ++ ++ switch (packet.getClickType()) { ++ case PICKUP: ++ if (packet.getButtonNum() == 0) { ++ click = ClickType.LEFT; ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { ++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves ++ if (packet.getSlotNum() == -999) { ++ if (!this.player.containerMenu.getCarried().isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; ++ } ++ } else if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null) { ++ ItemStack clickedItem = slot.getItem(); ++ ItemStack cursor = this.player.containerMenu.getCarried(); ++ if (clickedItem.isEmpty()) { ++ if (!cursor.isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE; ++ } ++ } else if (slot.mayPickup(this.player)) { ++ if (cursor.isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF; ++ } else if (slot.mayPlace(cursor)) { ++ if (ItemStack.isSameItemSameComponents(clickedItem, cursor)) { ++ int toPlace = packet.getButtonNum() == 0 ? cursor.getCount() : 1; ++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount()); ++ toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - 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.getMaxStackSize()) { ++ action = InventoryAction.SWAP_WITH_CURSOR; ++ } ++ } else if (ItemStack.isSameItemSameComponents(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 (packet.getButtonNum() == 0) { ++ click = ClickType.SHIFT_LEFT; ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.SHIFT_RIGHT; ++ } ++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) { ++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } ++ break; ++ case SWAP: ++ if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { ++ // Paper start - Add slot sanity checks to container clicks ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ break; ++ } ++ // Paper end - Add slot sanity checks to container clicks ++ click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; ++ Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (clickedSlot.mayPickup(this.player)) { ++ ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum()); ++ if ((!hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) || (hotbar.isEmpty() && clickedSlot.hasItem())) { // Paper - modernify this logic (no such thing as a "hotbar move and readd" ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ break; ++ case CLONE: ++ if (packet.getButtonNum() == 2) { ++ click = ClickType.MIDDLE; ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.hasItem() && this.player.getAbilities().instabuild && this.player.containerMenu.getCarried().isEmpty()) { ++ action = InventoryAction.CLONE_STACK; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } else { ++ click = ClickType.UNKNOWN; ++ action = InventoryAction.UNKNOWN; ++ } ++ break; ++ case THROW: ++ if (packet.getSlotNum() >= 0) { ++ if (packet.getButtonNum() == 0) { ++ click = ClickType.DROP; ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) { ++ action = InventoryAction.DROP_ONE_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.CONTROL_DROP; ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(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 (packet.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ action = InventoryAction.NOTHING; ++ } ++ break; ++ case QUICK_CRAFT: ++ // Paper start - Fix CraftBukkit drag system ++ AbstractContainerMenu containerMenu = this.player.containerMenu; ++ int currentStatus = this.player.containerMenu.quickcraftStatus; ++ int newStatus = AbstractContainerMenu.getQuickcraftHeader(packet.getButtonNum()); ++ if ((currentStatus != 1 || newStatus != 2 && currentStatus != newStatus)) { ++ } else if (containerMenu.getCarried().isEmpty()) { ++ } else if (newStatus == 0) { ++ } else if (newStatus == 1) { ++ } else if (newStatus == 2) { ++ if (!this.player.containerMenu.quickcraftSlots.isEmpty()) { ++ if (this.player.containerMenu.quickcraftSlots.size() == 1) { ++ int index = containerMenu.quickcraftSlots.iterator().next().index; ++ containerMenu.resetQuickCraft(); ++ this.handleContainerClick(new ServerboundContainerClickPacket(packet.getContainerId(), packet.getStateId(), index, containerMenu.quickcraftType, net.minecraft.world.inventory.ClickType.PICKUP, packet.getCarriedItem(), packet.getChangedSlots())); ++ return; ++ } ++ } ++ } ++ // Paper end - Fix CraftBukkit drag system ++ this.player.containerMenu.clicked(packet.getSlotNum(), packet.getButtonNum(), packet.getClickType(), this.player); ++ break; ++ case PICKUP_ALL: ++ click = ClickType.DOUBLE_CLICK; ++ action = InventoryAction.NOTHING; ++ if (packet.getSlotNum() >= 0 && !this.player.containerMenu.getCarried().isEmpty()) { ++ ItemStack cursor = this.player.containerMenu.getCarried(); ++ action = InventoryAction.NOTHING; ++ // Quick check for if we have any of the item ++ if (inventory.getTopInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem())) || inventory.getBottomInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem()))) { ++ action = InventoryAction.COLLECT_TO_CURSOR; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (packet.getClickType() != net.minecraft.world.inventory.ClickType.QUICK_CRAFT) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ ++ org.bukkit.inventory.Inventory top = inventory.getTopInventory(); ++ if (packet.getSlotNum() == 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, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ if (packet.getSlotNum() == 3 && top instanceof SmithingInventory) { ++ org.bukkit.inventory.ItemStack result = ((SmithingInventory) top).getResult(); ++ if (result != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ // Paper start - cartography item event ++ if (packet.getSlotNum() == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) { ++ org.bukkit.inventory.ItemStack result = cartographyInventory.getResult(); ++ if (result != null && !result.isEmpty()) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ // Paper end - cartography item event ++ ++ event.setCancelled(cancelled); ++ AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224 ++ this.cserver.getPluginManager().callEvent(event); ++ if (this.player.containerMenu != oldContainer) { ++ return; ++ } ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ case DEFAULT: ++ this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.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.containerMenu.sendAllDataToRemote(); ++ 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.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem())); ++ break; ++ // Modified clicked only ++ case DROP_ALL_SLOT: ++ case DROP_ONE_SLOT: ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem())); ++ break; ++ // Modified cursor only ++ case DROP_ALL_CURSOR: ++ case DROP_ONE_CURSOR: ++ case CLONE_STACK: ++ this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor ++ break; ++ // Nothing ++ case NOTHING: ++ break; ++ } ++ } ++ ++ if (event instanceof CraftItemEvent || event instanceof SmithItemEvent) { ++ // Need to update the inventory on crafting to ++ // correctly support custom recipes ++ this.player.containerMenu.sendAllDataToRemote(); ++ } ++ } ++ // CraftBukkit end + ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator(); + + while (objectiterator.hasNext()) { +@@ -1879,6 +3332,14 @@ + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ // Paper start - auto recipe limit ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ if (!this.recipeSpamPackets.isIncrementAndUnderThreshold()) { ++ this.disconnectAsync(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Paper - add proper async disconnect ++ return; ++ } ++ } ++ // Paper end - auto recipe limit + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.containerId()) { +@@ -1900,9 +3361,43 @@ + ServerGamePacketListenerImpl.LOGGER.debug("Player {} tried to place impossible recipe {}", this.player, recipeholder.id().location()); + return; + } ++ // Paper start - Add PlayerRecipeBookClickEvent ++ NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeholder.id().location()); ++ boolean makeAll = packet.useMaxItems(); ++ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( ++ this.player.getBukkitEntity(), recipeName, makeAll ++ ); ++ if (!paperEvent.callEvent()) { ++ return; ++ } ++ recipeName = paperEvent.getRecipe(); ++ makeAll = paperEvent.isMakeAll(); ++ if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ // Paper end - Add PlayerRecipeBookClickEvent + +- RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(packet.useMaxItems(), this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); ++ // CraftBukkit start - implement PlayerRecipeBookClickEvent ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event ++ if (recipe == null) { ++ return; ++ } ++ // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); ++ recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey(); ++ makeAll = event.isShiftClick(); ++ } ++ if (!(this.player.containerMenu instanceof RecipeBookMenu)) { ++ return; ++ } ++ // Paper end - Add PlayerRecipeBookClickEvent - forward to legacy event + ++ // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. ++ recipeholder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event ++ if (recipeholder == null) { ++ return; ++ } ++ RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(makeAll, this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); ++ // CraftBukkit end ++ + if (containerrecipebook_a == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) { + this.player.connection.send(new ClientboundPlaceGhostRecipePacket(this.player.containerMenu.containerId, craftingmanager_d.display().display())); + } +@@ -1917,6 +3412,7 @@ + @Override + public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); + if (this.player.containerMenu.containerId == packet.containerId() && !this.player.isSpectator()) { + if (!this.player.containerMenu.stillValid(this.player)) { +@@ -1945,7 +3441,44 @@ + + boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45; + boolean flag2 = itemstack.isEmpty() || itemstack.getCount() <= itemstack.getMaxStackSize(); ++ if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.slotNum()).getItem(), packet.itemStack()))) { // Insist on valid slot ++ // CraftBukkit start - Call click event ++ InventoryView inventory = this.player.inventoryMenu.getBukkitView(); ++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet.itemStack()); + ++ SlotType type = SlotType.QUICKBAR; ++ if (flag) { ++ type = SlotType.OUTSIDE; ++ } else if (packet.slotNum() < 36) { ++ if (packet.slotNum() >= 5 && packet.slotNum() < 9) { ++ type = SlotType.ARMOR; ++ } else { ++ type = SlotType.CONTAINER; ++ } ++ } ++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet.slotNum(), item); ++ this.cserver.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 (packet.slotNum() >= 0) { ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.slotNum(), this.player.inventoryMenu.getSlot(packet.slotNum()).getItem())); ++ this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(ItemStack.EMPTY.copy())); // Paper - correctly set cursor ++ } ++ return; ++ } ++ } ++ // CraftBukkit end ++ + if (flag1 && flag2) { + this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemstack); + this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemstack); +@@ -1964,7 +3497,19 @@ + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { +- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper start - Limit client sign length ++ String[] lines = packet.getLines(); ++ for (int i = 0; i < lines.length; ++i) { ++ if (MAX_SIGN_LINE_LENGTH > 0 && lines[i].length() > MAX_SIGN_LINE_LENGTH) { ++ // This handles multibyte characters as 1 ++ int offset = lines[i].codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); ++ if (offset < lines[i].length()) { ++ lines[i] = lines[i].substring(0, offset); // this will break any filtering, but filtering is NYI as of 1.17 ++ } ++ } ++ } ++ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper end - Limit client sign length + + this.filterTextPacket(list).thenAcceptAsync((list1) -> { + this.updateSignText(packet, list1); +@@ -1972,6 +3517,7 @@ + } + + private void updateSignText(ServerboundSignUpdatePacket packet, List signText) { ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); + ServerLevel worldserver = this.player.serverLevel(); + BlockPos blockposition = packet.getPos(); +@@ -1993,15 +3539,33 @@ + @Override + public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); +- this.player.getAbilities().flying = packet.isFlying() && this.player.getAbilities().mayfly; ++ // CraftBukkit start ++ if (this.player.getAbilities().mayfly && this.player.getAbilities().flying != packet.isFlying()) { ++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.player.getBukkitEntity(), packet.isFlying()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.player.getAbilities().flying = packet.isFlying(); // Actually set the player's flying status ++ } else { ++ this.player.onUpdateAbilities(); // Tell the player their ability was reverted ++ } ++ } ++ // CraftBukkit end + } + + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // Paper start - do not accept invalid information ++ if (packet.information().viewDistance() < 0) { ++ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); ++ this.disconnect(Component.literal("Invalid client settings"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); ++ return; ++ } ++ // Paper end - do not accept invalid information + boolean flag = this.player.isModelPartShown(PlayerModelPart.HAT); + + this.player.updateOptions(packet.information()); ++ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + if (this.player.isModelPartShown(PlayerModelPart.HAT) != flag) { + this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, this.player)); + } +@@ -2012,7 +3576,7 @@ + public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { +- this.server.setDifficulty(packet.getDifficulty(), false); ++ // this.server.setDifficulty(packet.getDifficulty(), false); // Paper - per level difficulty; don't allow clients to change this + } + } + +@@ -2033,7 +3597,7 @@ + + if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { + if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { +- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); ++ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes + } else { + try { + SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); +@@ -2045,8 +3609,8 @@ + + this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); + } catch (ProfilePublicKey.ValidationException profilepublickey_b) { +- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); +- this.disconnect(profilepublickey_b.getComponent()); ++ // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors ++ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes + } + + } +@@ -2058,7 +3622,7 @@ + if (!this.waitingForSwitchToConfig) { + throw new IllegalStateException("Client acknowledged config, but none was requested"); + } else { +- this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))); ++ this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit + } + } + +@@ -2076,15 +3640,18 @@ + + private void resetPlayerChatState(RemoteChatSession session) { + this.chatSession = session; ++ this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { + this.player.setChatSession(session); +- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); ++ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join + }); + } + +- @Override +- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {} ++ // CraftBukkit start - handled in super ++ // @Override ++ // public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) {} ++ // CraftBukkit end + + @Override + public void handleClientTickEnd(ServerboundClientTickEndPacket packet) { +@@ -2115,4 +3682,17 @@ + + InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); + } ++ ++ // Paper start - Add fail move event ++ private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, ++ double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); ++ Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, ++ false, logWarning, from, to); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - Add fail move event + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch new file mode 100644 index 0000000000..bf3a6ca144 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch @@ -0,0 +1,169 @@ +--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -13,11 +13,26 @@ + import net.minecraft.network.protocol.status.StatusProtocols; + import net.minecraft.server.MinecraftServer; + ++// CraftBukkit start ++import java.net.InetAddress; ++import java.util.HashMap; ++// CraftBukkit end ++ + public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener { + ++ // Spigot start ++ private static final com.google.gson.Gson gson = new com.google.gson.Gson(); ++ static final java.util.regex.Pattern HOST_PATTERN = java.util.regex.Pattern.compile("[0-9a-f\\.:]{0,45}"); ++ static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}"); ++ // Spigot end ++ // CraftBukkit start - add fields ++ private static final HashMap throttleTracker = new HashMap(); ++ private static int throttleCounter = 0; ++ // CraftBukkit end + private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request"); + private final MinecraftServer server; + private final Connection connection; ++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper + + public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { + this.server = server; +@@ -26,6 +41,7 @@ + + @Override + public void handleIntention(ClientIntentionPacket packet) { ++ this.connection.hostname = packet.hostName() + ":" + packet.port(); // CraftBukkit - set hostname + switch (packet.intention()) { + case LOGIN: + this.beginLogin(packet, false); +@@ -55,23 +71,127 @@ + throw new UnsupportedOperationException("Invalid intention " + String.valueOf(packet.intention())); + } + ++ // Paper start - NetworkClient implementation ++ this.connection.protocolVersion = packet.protocolVersion(); ++ this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port()); ++ // Paper end + } + + private void beginLogin(ClientIntentionPacket packet, boolean transfer) { + this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); ++ // CraftBukkit start - Connection throttle ++ try { ++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket ++ long currentTime = System.currentTimeMillis(); ++ long connectionThrottle = this.server.server.getConnectionThrottle(); ++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); ++ ++ synchronized (ServerHandshakePacketListenerImpl.throttleTracker) { ++ if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) { ++ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime); ++ Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message ++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.connection.disconnect(chatmessage); ++ return; ++ } ++ ++ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime); ++ ServerHandshakePacketListenerImpl.throttleCounter++; ++ if (ServerHandshakePacketListenerImpl.throttleCounter > 200) { ++ ServerHandshakePacketListenerImpl.throttleCounter = 0; ++ ++ // Cleanup stale entries ++ java.util.Iterator iter = ServerHandshakePacketListenerImpl.throttleTracker.entrySet().iterator(); ++ while (iter.hasNext()) { ++ java.util.Map.Entry entry = (java.util.Map.Entry) iter.next(); ++ if (entry.getValue() > connectionThrottle) { ++ iter.remove(); ++ } ++ } ++ } ++ } ++ } // Paper - Unix domain socket support ++ } catch (Throwable t) { ++ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); ++ } ++ // CraftBukkit end + if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- MutableComponent ichatmutablecomponent; ++ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages + +- if (packet.protocolVersion() < 754) { +- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName()); ++ if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName()); ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } + ++ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages ++ + this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); + this.connection.disconnect((Component) ichatmutablecomponent); + } else { + this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transfer)); ++ // Paper start - PlayerHandshakeEvent ++ 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? ++ java.net.SocketAddress socketAddress = this.connection.address; ++ String hostnameOfRemote = socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getHostString() : InetAddress.getLoopbackAddress().getHostAddress(); ++ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packet.hostName(), hostnameOfRemote, !proxyLogicEnabled); ++ if (event.callEvent()) { ++ // If we've failed somehow, let the client know so and go no further. ++ if (event.isFailed()) { ++ Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.failMessage()); ++ this.connection.send(new ClientboundLoginDisconnectPacket(component)); ++ this.connection.disconnect(component); ++ return; ++ } ++ ++ if (event.getServerHostname() != null) { ++ // change hostname ++ packet = new ClientIntentionPacket( ++ packet.protocolVersion(), ++ event.getServerHostname(), ++ packet.port(), ++ packet.intention() ++ ); ++ } ++ if (event.getSocketAddressHostname() != null) this.connection.address = new java.net.InetSocketAddress(event.getSocketAddressHostname(), socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ this.connection.spoofedUUID = event.getUniqueId(); ++ this.connection.spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class); ++ handledByEvent = true; // Hooray, we did it! ++ } ++ } ++ // Paper end ++ // Spigot Start ++ String[] split = packet.hostName().split("\00"); ++ if (!handledByEvent && proxyLogicEnabled) { // Paper ++ // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! ++ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); ++ this.connection.hostname = split[0]; ++ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ // Paper end - Unix domain socket support ++ this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); ++ } else ++ { ++ Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"); ++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.connection.disconnect(chatmessage); ++ return; ++ } ++ if ( split.length == 4 ) ++ { ++ this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class); ++ } ++ } else if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { ++ Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); ++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.connection.disconnect(chatmessage); ++ return; ++ } ++ // Spigot End + } + + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch new file mode 100644 index 0000000000..2f73ee117e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch @@ -0,0 +1,424 @@ +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -20,6 +20,7 @@ + import net.minecraft.DefaultUncaughtExceptionHandler; + import net.minecraft.core.UUIDUtil; + import net.minecraft.network.Connection; ++import net.minecraft.network.ConnectionProtocol; + import net.minecraft.network.DisconnectionDetails; + import net.minecraft.network.PacketSendListener; + import net.minecraft.network.TickablePacketListener; +@@ -36,6 +37,7 @@ + import net.minecraft.network.protocol.login.ServerboundKeyPacket; + import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.players.PlayerList; + import net.minecraft.util.Crypt; + import net.minecraft.util.CryptException; +@@ -43,11 +45,38 @@ + import net.minecraft.util.StringUtil; + import org.apache.commons.lang3.Validate; + import org.slf4j.Logger; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.PacketUtils; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.event.player.AsyncPlayerPreLoginEvent; ++import org.bukkit.event.player.PlayerPreLoginEvent; + +-public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { ++public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection { + ++ @Override ++ public boolean isTransferred() { ++ return this.transferred; ++ } ++ ++ @Override ++ public ConnectionProtocol getProtocol() { ++ return ConnectionProtocol.LOGIN; ++ } ++ ++ @Override ++ public void sendPacket(Packet packet) { ++ this.connection.send(packet); ++ } ++ ++ @Override ++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called. ++ this.disconnect(reason); ++ } ++ // CraftBukkit end + private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); + static final Logger LOGGER = LogUtils.getLogger(); ++ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads + private static final int MAX_TICKS_BEFORE_LOGIN = 600; + private final byte[] challenge; + final MinecraftServer server; +@@ -57,9 +86,12 @@ + @Nullable + String requestedUsername; + @Nullable +- private GameProfile authenticatedProfile; ++ public GameProfile authenticatedProfile; // Paper - public + private final String serverId; + private final boolean transferred; ++ private ServerPlayer player; // CraftBukkit ++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding ++ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -72,10 +104,24 @@ + + @Override + public void tick() { ++ // Paper start - Do not allow logins while the server is shutting down ++ if (!MinecraftServer.getServer().isRunning()) { ++ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]); ++ return; ++ } ++ // Paper end - Do not allow logins while the server is shutting down + if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { ++ if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called + this.verifyLoginAndFinishConnectionSetup((GameProfile) Objects.requireNonNull(this.authenticatedProfile)); ++ } // Paper - prevent logins to be processed even though disconnect was called + } + ++ // CraftBukkit start ++ if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) { ++ this.postCookies(this.authenticatedProfile); ++ } ++ // CraftBukkit end ++ + if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) { + this.finishLoginAndWaitForClient(this.authenticatedProfile); + } +@@ -86,6 +132,13 @@ + + } + ++ // CraftBukkit start ++ @Deprecated ++ public void disconnect(String s) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages ++ } ++ // CraftBukkit end ++ + @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected(); +@@ -120,7 +173,13 @@ + @Override + public void handleHello(ServerboundHelloPacket packet) { + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); +- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ // Paper start - Validate usernames ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ++ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation ++ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { ++ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ } ++ // Paper end - Validate usernames + this.requestedUsername = packet.name(); + GameProfile gameprofile = this.server.getSingleplayerProfile(); + +@@ -131,7 +190,36 @@ + this.state = ServerLoginPacketListenerImpl.State.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true)); + } else { +- this.startClientVerification(UUIDUtil.createOfflineProfile(this.requestedUsername)); ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); ++ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); ++ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf)); ++ this.connection.send(packet1); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support ++ // CraftBukkit start ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { ++ ++ @Override ++ public void run() { ++ try { ++ GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot ++ ++ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ } catch (Exception ex) { ++ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); ++ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex); ++ } ++ } ++ }); ++ // Paper end - Cache authenticator threads ++ // CraftBukkit end + } + + } +@@ -144,10 +232,24 @@ + + private void verifyLoginAndFinishConnectionSetup(GameProfile profile) { + PlayerList playerlist = this.server.getPlayerList(); +- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), profile); ++ // CraftBukkit start - fire PlayerLoginEvent ++ this.player = playerlist.canPlayerLogin(this, profile); // CraftBukkit + +- if (ichatbasecomponent != null) { +- this.disconnect(ichatbasecomponent); ++ if (this.player != null) { ++ if (this.player.getBukkitEntity().isAwaitingCookies()) { ++ this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES; ++ } else { ++ this.postCookies(profile); ++ } ++ } ++ } ++ ++ private void postCookies(GameProfile gameprofile) { ++ PlayerList playerlist = this.server.getPlayerList(); ++ ++ if (this.player == null) { ++ // this.disconnect(ichatbasecomponent); ++ // CraftBukkit end + } else { + if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) { + this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> { +@@ -155,12 +257,12 @@ + })); + } + +- boolean flag = playerlist.disconnectAllPlayersWithProfile(profile); ++ boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference + + if (flag) { + this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT; + } else { +- this.finishLoginAndWaitForClient(profile); ++ this.finishLoginAndWaitForClient(gameprofile); + } + } + +@@ -195,7 +297,8 @@ + throw new IllegalStateException("Protocol error", cryptographyexception); + } + +- Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) { ++ // Paper start - Cache authenticator threads ++ authenticatorPool.execute(new Runnable() { + public void run() { + String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); + +@@ -205,11 +308,17 @@ + if (profileresult != null) { + GameProfile gameprofile = profileresult.profile(); + ++ // CraftBukkit start - fire PlayerPreLoginEvent ++ if (!ServerLoginPacketListenerImpl.this.connection.isConnected()) { ++ return; ++ } ++ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ // CraftBukkit end + ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); + ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); + } else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { + ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); +- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1)); ++ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot + } else { + ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1); +@@ -217,11 +326,16 @@ + } catch (AuthenticationUnavailableException authenticationunavailableexception) { + if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { + ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!"); +- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1)); ++ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot + } else { +- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); ++ ServerLoginPacketListenerImpl.this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.authenticationServersDown)); // Paper - Configurable kick message + ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable"); + } ++ // CraftBukkit start - catch all exceptions ++ } catch (Exception exception) { ++ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); ++ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception); ++ // CraftBukkit end + } + + } +@@ -232,23 +346,118 @@ + + return ServerLoginPacketListenerImpl.this.server.getPreventProxyConnections() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null; + } +- }; ++ }); ++ // Paper end - Cache authenticator threads ++ } + +- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(ServerLoginPacketListenerImpl.LOGGER)); +- thread.start(); ++ // CraftBukkit start ++ private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ // Paper start - Add Velocity IP Forwarding Support ++ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ disconnect("This server requires you to connect with Velocity."); ++ return gameprofile; ++ } ++ // Paper end - Add Velocity IP Forwarding Support ++ String playerName = gameprofile.getName(); ++ java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); ++ java.util.UUID uniqueId = gameprofile.getId(); ++ final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; ++ ++ // Paper start - Add more fields to AsyncPlayerPreLoginEvent ++ final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress(); ++ com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, this.transferred, profile, this.connection.hostname); ++ server.getPluginManager().callEvent(asyncEvent); ++ profile = asyncEvent.getPlayerProfile(); ++ profile.complete(true); // Paper - setPlayerProfileAPI ++ gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile); ++ playerName = gameprofile.getName(); ++ uniqueId = gameprofile.getId(); ++ // Paper end - Add more fields to AsyncPlayerPreLoginEvent ++ ++ 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.kickMessage()); // Paper - Adventure ++ } ++ Waitable waitable = new Waitable() { ++ @Override ++ protected PlayerPreLoginEvent.Result evaluate() { ++ server.getPluginManager().callEvent(event); ++ return event.getResult(); ++ } ++ }; ++ ++ ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable); ++ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure ++ } ++ } else { ++ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(asyncEvent.kickMessage())); // Paper - Adventure ++ } ++ } ++ return gameprofile; // Paper - Add more fields to AsyncPlayerPreLoginEvent + } ++ // CraftBukkit end + + @Override + public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { ++ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); ++ if (payload == null) { ++ this.disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ ++ net.minecraft.network.FriendlyByteBuf buf = payload.buffer; ++ ++ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { ++ this.disconnect("Unable to verify player details"); ++ return; ++ } ++ ++ int version = buf.readVarInt(); ++ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ java.net.SocketAddress listening = this.connection.getRemoteAddress(); ++ int port = 0; ++ if (listening instanceof java.net.InetSocketAddress) { ++ port = ((java.net.InetSocketAddress) listening).getPort(); ++ } ++ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); ++ ++ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); ++ ++ //TODO Update handling for lazy sessions, might not even have to do anything? ++ ++ // Proceed with login ++ authenticatorPool.execute(() -> { ++ try { ++ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); ++ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + + @Override + public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]); + this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile), this.transferred); +- ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie); ++ ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit + + this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverconfigurationpacketlistenerimpl); + serverconfigurationpacketlistenerimpl.startConfiguration(); +@@ -264,12 +473,44 @@ + + @Override + public void handleCookieResponse(ServerboundCookieResponsePacket packet) { ++ // CraftBukkit start ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); ++ if (this.player != null && this.player.getBukkitEntity().handleCookieResponse(packet)) { ++ return; ++ } ++ // CraftBukkit end + this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + ++ // Spigot start ++ protected GameProfile createOfflineProfile(String s) { ++ java.util.UUID uuid; ++ if ( this.connection.spoofedUUID != null ) ++ { ++ uuid = this.connection.spoofedUUID; ++ } else ++ { ++ uuid = UUIDUtil.createOfflinePlayerUUID( s ); ++ } ++ ++ GameProfile gameProfile = new GameProfile( uuid, s ); ++ ++ if (this.connection.spoofedProfile != null) ++ { ++ for ( com.mojang.authlib.properties.Property property : this.connection.spoofedProfile ) ++ { ++ if ( !ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher( property.name()).matches() ) continue; ++ gameProfile.getProperties().put( property.name(), property ); ++ } ++ } ++ ++ return gameProfile; ++ } ++ // Spigot end ++ + public static enum State { + +- HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; ++ HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_COOKIES, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; // CraftBukkit + + private State() {} + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch new file mode 100644 index 0000000000..7ea7e46622 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch @@ -0,0 +1,137 @@ +--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -9,6 +9,19 @@ + import net.minecraft.network.protocol.status.ServerStatus; + import net.minecraft.network.protocol.status.ServerStatusPacketListener; + import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; ++// CraftBukkit start ++import com.mojang.authlib.GameProfile; ++import java.net.InetSocketAddress; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.Optional; ++import net.minecraft.SharedConstants; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftIconCache; ++import org.bukkit.entity.Player; ++// CraftBukkit end + + public class ServerStatusPacketListenerImpl implements ServerStatusPacketListener { + +@@ -36,7 +49,113 @@ + this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON); + } else { + this.hasRequestedStatus = true; +- this.connection.send(new ClientboundStatusResponsePacket(this.status)); ++ // Paper start - Replace everything ++ /* ++ // CraftBukkit start ++ // this.connection.send(new PacketStatusOutServerInfo(this.status)); ++ MinecraftServer server = MinecraftServer.getServer(); ++ final Object[] players = server.getPlayerList().players.toArray(); ++ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent { ++ ++ CraftIconCache icon = server.server.getServerIcon(); ++ ++ ServerListPingEvent() { ++ super(ServerStatusPacketListenerImpl.this.connection.hostname, ((InetSocketAddress) ServerStatusPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure ++ } ++ ++ @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; ++ ServerPlayer player; ++ ++ @Override ++ public boolean hasNext() { ++ if (this.player != null) { ++ return true; ++ } ++ final Object[] currentPlayers = players; ++ for (int length = currentPlayers.length, i = this.i; i < length; i++) { ++ final ServerPlayer player = (ServerPlayer) currentPlayers[i]; ++ if (player != null) { ++ this.i = i + 1; ++ this.player = player; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Player next() { ++ if (!this.hasNext()) { ++ throw new java.util.NoSuchElementException(); ++ } ++ final ServerPlayer 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(); ++ server.server.getPluginManager().callEvent(event); ++ ++ java.util.List profiles = new java.util.ArrayList(players.length); ++ for (Object player : players) { ++ if (player != null) { ++ ServerPlayer entityPlayer = ((ServerPlayer) player); ++ if (entityPlayer.allowsListing()) { ++ profiles.add(entityPlayer.getGameProfile()); ++ } else { ++ profiles.add(MinecraftServer.ANONYMOUS_PLAYER_PROFILE); ++ } ++ } ++ } ++ ++ // Spigot Start ++ if ( !server.hidesOnlinePlayers() && !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 ++ ServerStatus.Players playerSample = new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), (server.hidesOnlinePlayers()) ? Collections.emptyList() : profiles); ++ ++ ServerStatus ping = new ServerStatus( ++ CraftChatMessage.fromString(event.getMotd(), true)[0], ++ Optional.of(playerSample), ++ Optional.of(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), SharedConstants.getCurrentVersion().getProtocolVersion())), ++ (event.icon.value != null) ? Optional.of(new ServerStatus.Favicon(event.icon.value)) : Optional.empty(), ++ server.enforceSecureProfile() ++ ); ++ ++ this.connection.send(new ClientboundStatusResponsePacket(ping)); ++ // CraftBukkit end ++ */ ++ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection); ++ // Paper end + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch new file mode 100644 index 0000000000..4c21d9802f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/server/packs/PathPackResources.java ++++ b/net/minecraft/server/packs/PathPackResources.java +@@ -103,6 +103,12 @@ + try (DirectoryStream directoryStream = Files.newDirectoryStream(path)) { + for (Path path2 : directoryStream) { + String string = path2.getFileName().toString(); ++ // Paper start - Improve logging and errors ++ if (!Files.isDirectory(path2)) { ++ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string)); ++ continue; ++ } ++ // Paper end - Improve logging and errors + if (ResourceLocation.isValidNamespace(string)) { + set.add(string); + } else { diff --git a/paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch new file mode 100644 index 0000000000..5f4418300b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/server/packs/VanillaPackResourcesBuilder.java ++++ b/net/minecraft/server/packs/VanillaPackResourcesBuilder.java +@@ -138,6 +138,15 @@ + + public VanillaPackResourcesBuilder applyDevelopmentConfig() { + developmentConfig.accept(this); ++ if (Boolean.getBoolean("Paper.pushPaperAssetsRoot")) { ++ try { ++ this.pushAssetPath(net.minecraft.server.packs.PackType.SERVER_DATA, net.minecraft.server.packs.VanillaPackResourcesBuilder.safeGetPath(java.util.Objects.requireNonNull( ++ // Important that this is a patched class ++ VanillaPackResourcesBuilder.class.getResource("/data/.paperassetsroot"), "Missing required .paperassetsroot file").toURI()).getParent()); ++ } catch (java.net.URISyntaxException | IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ } + return this; + } + diff --git a/paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch new file mode 100644 index 0000000000..a5d66bbff8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/server/packs/repository/ServerPacksSource.java ++++ b/net/minecraft/server/packs/repository/ServerPacksSource.java +@@ -48,7 +48,7 @@ + public static VanillaPackResources createVanillaPackSource() { + return new VanillaPackResourcesBuilder() + .setMetadata(BUILT_IN_METADATA) +- .exposeNamespace("minecraft") ++ .exposeNamespace("minecraft", ResourceLocation.PAPER_NAMESPACE) // Paper + .applyDevelopmentConfig() + .pushJarResources() + .build(VANILLA_PACK_INFO); +@@ -68,7 +68,18 @@ + @Nullable + @Override + protected Pack createBuiltinPack(String fileName, Pack.ResourcesSupplier packFactory, Component displayName) { +- return Pack.readMetaAndCreate(createBuiltInPackLocation(fileName, displayName), packFactory, PackType.SERVER_DATA, FEATURE_SELECTION_CONFIG); ++ // Paper start - custom built-in pack ++ final PackLocationInfo info; ++ final PackSelectionConfig packConfig; ++ if ("paper".equals(fileName)) { ++ info = new PackLocationInfo(fileName, displayName, PackSource.BUILT_IN, Optional.empty()); ++ packConfig = new PackSelectionConfig(true, Pack.Position.TOP, true); ++ } else { ++ info = createBuiltInPackLocation(fileName, displayName); ++ packConfig = FEATURE_SELECTION_CONFIG; ++ } ++ return Pack.readMetaAndCreate(info, packFactory, PackType.SERVER_DATA, packConfig); ++ // Paper end - custom built-in pack + } + + public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) { diff --git a/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch new file mode 100644 index 0000000000..205eaad746 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/server/players/BanListEntry.java ++++ b/net/minecraft/server/players/BanListEntry.java +@@ -27,7 +27,7 @@ + } + + protected BanListEntry(@Nullable T key, JsonObject json) { +- super(key); ++ super(BanListEntry.checkExpiry(key, json)); // CraftBukkit + + Date date; + +@@ -83,4 +83,22 @@ + json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires)); + json.addProperty("reason", this.reason); + } ++ ++ // CraftBukkit start ++ private static T checkExpiry(T object, JsonObject jsonobject) { ++ Date expires = null; ++ ++ try { ++ expires = jsonobject.has("expires") ? BanListEntry.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; ++ } ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch new file mode 100644 index 0000000000..8b31e2142b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch @@ -0,0 +1,217 @@ +--- a/net/minecraft/server/players/GameProfileCache.java ++++ b/net/minecraft/server/players/GameProfileCache.java +@@ -60,6 +60,11 @@ + @Nullable + private Executor executor; + ++ // Paper start - Fix GameProfileCache concurrency ++ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); ++ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); ++ // Paper end - Fix GameProfileCache concurrency ++ + public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { + this.profileRepository = profileRepository; + this.file = cacheFile; +@@ -67,11 +72,13 @@ + } + + private void safeAdd(GameProfileCache.GameProfileInfo entry) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfile gameprofile = entry.getProfile(); + + entry.setLastAccess(this.getNextOperation()); + this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry); + this.profilesByUUID.put(gameprofile.getId(), entry); ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency + } + + private static Optional lookupGameProfile(GameProfileRepository repository, String name) { +@@ -85,10 +92,12 @@ + } + + public void onProfileLookupFailed(String s1, Exception exception) { +- atomicreference.set((Object) null); ++ atomicreference.set(null); // CraftBukkit - decompile error + } + }; + ++ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status + repository.findProfilesByNames(new String[]{name}, profilelookupcallback); + GameProfile gameprofile = (GameProfile) atomicreference.get(); + +@@ -105,7 +114,7 @@ + } + + private static boolean usesAuthentication() { +- return GameProfileCache.usesAuthentication; ++ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status + } + + public void add(GameProfile profile) { +@@ -117,15 +126,29 @@ + GameProfileCache.GameProfileInfo usercache_usercacheentry = new GameProfileCache.GameProfileInfo(profile, date); + + this.safeAdd(usercache_usercacheentry); +- this.save(); ++ if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving + } + + private long getNextOperation() { + return this.operationCount.incrementAndGet(); + } + ++ // Paper start ++ public @Nullable GameProfile getProfileIfCached(String name) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency ++ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); ++ if (entry == null) { ++ return null; ++ } ++ entry.setLastAccess(this.getNextOperation()); ++ return entry.getProfile(); ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency ++ } ++ // Paper end ++ + public Optional get(String name) { + String s1 = name.toLowerCase(Locale.ROOT); ++ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); + boolean flag = false; + +@@ -141,19 +164,24 @@ + if (usercache_usercacheentry != null) { + usercache_usercacheentry.setLastAccess(this.getNextOperation()); + optional = Optional.of(usercache_usercacheentry.getProfile()); ++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency + } else { +- optional = GameProfileCache.lookupGameProfile(this.profileRepository, s1); ++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency ++ try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency ++ optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players ++ } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency + if (optional.isPresent()) { + this.add((GameProfile) optional.get()); + flag = false; + } + } + +- if (flag) { +- this.save(); ++ if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled ++ this.save(true); // Paper - Perf: Async GameProfileCache saving + } + + return optional; ++ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency + } + + public CompletableFuture> getAsync(String username) { +@@ -167,7 +195,7 @@ + } else { + CompletableFuture> completablefuture1 = CompletableFuture.supplyAsync(() -> { + return this.get(username); +- }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> { ++ }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + this.requests.remove(username); + }, this.executor); + +@@ -178,6 +206,7 @@ + } + + public Optional get(UUID uuid) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); + + if (usercache_usercacheentry == null) { +@@ -186,6 +215,7 @@ + usercache_usercacheentry.setLastAccess(this.getNextOperation()); + return Optional.of(usercache_usercacheentry.getProfile()); + } ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency + } + + public void setExecutor(Executor executor) { +@@ -208,7 +238,7 @@ + + label54: + { +- ArrayList arraylist; ++ List arraylist; // CraftBukkit - decompile error + + try { + JsonArray jsonarray = (JsonArray) this.gson.fromJson(bufferedreader, JsonArray.class); +@@ -217,7 +247,7 @@ + DateFormat dateformat = GameProfileCache.createDateFormat(); + + jsonarray.forEach((jsonelement) -> { +- Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); ++ Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); // CraftBukkit - decompile error + + Objects.requireNonNull(list); + optional.ifPresent(list::add); +@@ -250,6 +280,11 @@ + } + } catch (FileNotFoundException filenotfoundexception) { + ; ++ // Spigot Start ++ } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) { ++ GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." ); ++ this.file.delete(); ++ // Spigot End + } catch (JsonParseException | IOException ioexception) { + GameProfileCache.LOGGER.warn("Failed to load profile cache {}", this.file, ioexception); + } +@@ -257,14 +292,15 @@ + return list; + } + +- public void save() { ++ public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving + JsonArray jsonarray = new JsonArray(); + DateFormat dateformat = GameProfileCache.createDateFormat(); + +- this.getTopMRUProfiles(1000).forEach((usercache_usercacheentry) -> { ++ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency + jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat)); + }); + String s = this.gson.toJson(jsonarray); ++ Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving + + try { + BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); +@@ -289,13 +325,32 @@ + } catch (IOException ioexception) { + ; + } ++ // Paper start - Perf: Async GameProfileCache saving ++ }; ++ if (asyncSave) { ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); ++ } else { ++ save.run(); ++ } ++ // Paper end - Perf: Async GameProfileCache saving + + } + + private Stream getTopMRUProfiles(int limit) { +- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); ++ // Paper start - Fix GameProfileCache concurrency ++ return this.listTopMRUProfiles(limit).stream(); + } + ++ private List listTopMRUProfiles(int limit) { ++ try { ++ this.stateLock.lock(); ++ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList(); ++ } finally { ++ this.stateLock.unlock(); ++ } ++ } ++ // Paper end - Fix GameProfileCache concurrency ++ + private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { + JsonObject jsonobject = new JsonObject(); + diff --git a/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch new file mode 100644 index 0000000000..7f3147ad37 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch @@ -0,0 +1,98 @@ +--- a/net/minecraft/server/players/OldUsersConverter.java ++++ b/net/minecraft/server/players/OldUsersConverter.java +@@ -21,6 +21,9 @@ + import java.util.UUID; + import javax.annotation.Nullable; + import net.minecraft.core.UUIDUtil; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtAccounter; ++import net.minecraft.nbt.NbtIo; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.util.StringUtil; +@@ -62,7 +65,8 @@ + return new String[i]; + }); + +- if (server.usesAuthentication()) { ++ if (server.usesAuthentication() || ++ (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Add setting for proxy online mode status + server.getProfileRepository().findProfilesByNames(astring, callback); + } else { + String[] astring1 = astring; +@@ -85,7 +89,7 @@ + try { + gameprofilebanlist.load(); + } catch (IOException ioexception) { +- OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName(), ioexception); ++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace + } + } + +@@ -143,7 +147,7 @@ + try { + ipbanlist.load(); + } catch (IOException ioexception) { +- OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName(), ioexception); ++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace + } + } + +@@ -184,7 +188,7 @@ + try { + oplist.load(); + } catch (IOException ioexception) { +- OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName(), ioexception); ++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace + } + } + +@@ -228,7 +232,7 @@ + try { + whitelist.load(); + } catch (IOException ioexception) { +- OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName(), ioexception); ++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace + } + } + +@@ -346,7 +350,39 @@ + private void movePlayerFile(File playerDataFolder, String fileName, String uuid) { + File file5 = new File(file, fileName + ".dat"); + File file6 = new File(playerDataFolder, uuid + ".dat"); ++ ++ // CraftBukkit start - Use old file name to seed lastKnownName ++ CompoundTag root = null; ++ ++ try { ++ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap()); ++ } catch (Exception exception) { ++ // Paper start ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); ++ exception.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); ++ // Paper end ++ } + ++ if (root != null) { ++ if (!root.contains("bukkit")) { ++ root.put("bukkit", new CompoundTag()); ++ } ++ CompoundTag data = root.getCompound("bukkit"); ++ data.putString("lastKnownName", fileName); ++ ++ try { ++ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); ++ } catch (Exception exception) { ++ // Paper start ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); ++ exception.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); ++ // Paper end ++ } ++ } ++ // CraftBukkit end ++ + OldUsersConverter.ensureDirectoryExists(playerDataFolder); + if (!file5.renameTo(file6)) { + throw new OldUsersConverter.ConversionError("Could not convert file for " + fileName); diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch new file mode 100644 index 0000000000..06288fc4a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch @@ -0,0 +1,1276 @@ +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -76,6 +76,7 @@ + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.CommonListenerCookie; + import net.minecraft.server.network.ServerGamePacketListenerImpl; ++import net.minecraft.server.network.ServerLoginPacketListenerImpl; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.stats.ServerStatsCounter; +@@ -84,7 +85,6 @@ + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.LivingEntity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.item.crafting.RecipeManager; + import net.minecraft.world.level.GameRules; +@@ -104,6 +104,26 @@ + import net.minecraft.world.scores.PlayerTeam; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.stream.Collectors; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerChangedWorldEvent; ++import org.bukkit.event.player.PlayerJoinEvent; ++import org.bukkit.event.player.PlayerLoginEvent; ++import org.bukkit.event.player.PlayerQuitEvent; ++import org.bukkit.event.player.PlayerRespawnEvent; ++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; ++import org.bukkit.event.player.PlayerSpawnChangeEvent; ++// CraftBukkit end ++ + public abstract class PlayerList { + + public static final File USERBANLIST_FILE = new File("banned-players.json"); +@@ -116,14 +136,16 @@ + private static final int SEND_PLAYER_INFO_INTERVAL = 600; + private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); + private final MinecraftServer server; +- public final List players = Lists.newArrayList(); ++ public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety + private final Map playersByUUID = Maps.newHashMap(); + private final UserBanList bans; + private final IpBanList ipBans; + private final ServerOpList ops; + private final UserWhiteList whitelist; +- private final Map stats; +- private final Map advancements; ++ // CraftBukkit start ++ // private final Map stats; ++ // private final Map advancements; ++ // CraftBukkit end + public final PlayerDataStorage playerIo; + private boolean doWhiteList; + private final LayeredRegistryAccess registries; +@@ -134,58 +156,137 @@ + private static final boolean ALLOW_LOGOUTIVATOR = false; + private int sendAllPlayerInfoIn; + ++ // CraftBukkit start ++ private CraftServer cserver; ++ private final Map playersByName = new java.util.HashMap<>(); ++ public @Nullable String collideRuleTeamName; // Paper - Configurable player collision ++ + public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { ++ this.cserver = server.server = new CraftServer((DedicatedServer) server, this); ++ server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper ++ // CraftBukkit end ++ + this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); + this.ipBans = new IpBanList(PlayerList.IPBANLIST_FILE); + this.ops = new ServerOpList(PlayerList.OPLIST_FILE); + this.whitelist = new UserWhiteList(PlayerList.WHITELIST_FILE); +- this.stats = Maps.newHashMap(); +- this.advancements = Maps.newHashMap(); ++ // CraftBukkit start ++ // this.stats = Maps.newHashMap(); ++ // this.advancements = Maps.newHashMap(); ++ // CraftBukkit end + this.server = server; + this.registries = registryManager; + this.maxPlayers = maxPlayers; + this.playerIo = saveHandler; + } ++ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { ++ player.isRealPlayer = true; // Paper ++ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); +- Optional optional; ++ // Optional optional; // CraftBukkit - decompile error + String s; + + if (usercache != null) { +- optional = usercache.get(gameprofile.getId()); ++ Optional optional = usercache.get(gameprofile.getId()); // CraftBukkit - decompile error + s = (String) optional.map(GameProfile::getName).orElse(gameprofile.getName()); + usercache.add(gameprofile); + } else { + s = gameprofile.getName(); + } + +- optional = this.load(player); +- ResourceKey resourcekey = (ResourceKey) optional.flatMap((nbttagcompound) -> { +- DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); ++ Optional optional = this.load(player); // CraftBukkit - decompile error ++ ResourceKey resourcekey = null; // Paper ++ // CraftBukkit start - Better rename detection ++ if (optional.isPresent()) { ++ CompoundTag nbttagcompound = optional.get(); ++ if (nbttagcompound.contains("bukkit")) { ++ CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); ++ s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; ++ } ++ } ++ // CraftBukkit end ++ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found ++ boolean[] invalidPlayerWorld = {false}; ++ bukkitData: if (optional.isPresent()) { ++ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds ++ final org.bukkit.World bWorld; ++ if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) { ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast"))); ++ } else if (optional.get().contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(optional.get().getString("world")); ++ } else { ++ break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section ++ } ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); ++ } else { ++ resourcekey = Level.OVERWORLD; ++ invalidPlayerWorld[0] = true; ++ } ++ } ++ if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data ++ // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers ++ resourcekey = optional.flatMap((nbttagcompound) -> { ++ // Paper end ++ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error + Logger logger = PlayerList.LOGGER; + + Objects.requireNonNull(logger); +- return dataresult.resultOrPartial(logger::error); +- }).orElse(Level.OVERWORLD); ++ // Paper start - reset to main world spawn if no valid world is found ++ final Optional> result = dataresult.resultOrPartial(logger::error); ++ invalidPlayerWorld[0] = result.isEmpty(); ++ return result; ++ }).orElse(Level.OVERWORLD); // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed ++ } ++ // Paper end + ServerLevel worldserver = this.server.getLevel(resourcekey); + ServerLevel worldserver1; + + if (worldserver == null) { + PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey); + worldserver1 = this.server.overworld(); ++ invalidPlayerWorld[0] = true; // Paper - reset to main world if no world with parsed value is found + } else { + worldserver1 = worldserver; + } + ++ // Paper start - Entity#getEntitySpawnReason ++ if (optional.isEmpty()) { ++ player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ // Paper start - reset to main world spawn if first spawn or invalid world ++ } ++ if (optional.isEmpty() || invalidPlayerWorld[0]) { ++ // Paper end - reset to main world spawn if first spawn or invalid world ++ player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), worldserver1.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored ++ } ++ // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); + String s1 = connection.getLoggableAddress(this.server.logIPs()); + +- PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{player.getName().getString(), s1, player.getId(), player.getX(), player.getY(), player.getZ()}); ++ // Spigot start - spawn location event ++ Player spawnPlayer = player.getBukkitEntity(); ++ org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(spawnPlayer, spawnPlayer.getLocation()); ++ this.cserver.getPluginManager().callEvent(ev); ++ ++ Location loc = ev.getSpawnLocation(); ++ worldserver1 = ((CraftWorld) loc.getWorld()).getHandle(); ++ ++ player.spawnIn(worldserver1); ++ player.gameMode.setLevel((ServerLevel) player.level()); ++ // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world) ++ player.setPosRaw(loc.getX(), loc.getY(), loc.getZ()); ++ player.setRot(loc.getYaw(), loc.getPitch()); ++ // Paper end - set raw so we aren't fully joined to the world ++ // Spigot end ++ ++ // CraftBukkit - Moved message to after join ++ // PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{entityplayer.getName().getString(), s1, entityplayer.getId(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()}); + LevelData worlddata = worldserver1.getLevelData(); + +- player.loadGameTypes((CompoundTag) optional.orElse((Object) null)); ++ player.loadGameTypes((CompoundTag) optional.orElse(null)); // CraftBukkit - decompile error + ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, clientData); + + connection.setupInboundProtocol(GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), playerconnection); +@@ -194,7 +295,9 @@ + boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); + boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); + +- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), this.viewDistance, this.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); ++ // Spigot - view distance ++ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); ++ player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); + playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); + playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected)); +@@ -213,8 +316,10 @@ + } else { + ichatmutablecomponent = Component.translatable("multiplayer.player.joined.renamed", player.getDisplayName(), s); + } ++ // CraftBukkit start ++ ichatmutablecomponent.withStyle(ChatFormatting.YELLOW); ++ Component joinMessage = ichatmutablecomponent; // Paper - Adventure + +- this.broadcastSystemMessage(ichatmutablecomponent.withStyle(ChatFormatting.YELLOW), false); + playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); + ServerStatus serverping = this.server.getStatus(); + +@@ -222,17 +327,109 @@ + player.sendServerStatus(serverping); + } + +- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); ++ // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below + this.players.add(player); ++ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot + this.playersByUUID.put(player.getUUID(), player); +- this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); +- this.sendLevelInfo(player, worldserver1); ++ // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below ++ ++ // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks ++ player.supressTrackerForLogin = true; + worldserver1.addNewPlayer(player); +- this.server.getCustomBossEvents().onPlayerConnect(player); +- this.sendActivePlayerEffects(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); + player.loadAndSpawnEnderpearls(optional); + player.loadAndSpawnParentVehicle(optional); ++ // Paper end - Fire PlayerJoinEvent when Player is actually ready ++ // CraftBukkit start ++ CraftPlayer bukkitPlayer = player.getBukkitEntity(); ++ ++ // Ensure that player inventory is populated with its viewer ++ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); ++ ++ PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(bukkitPlayer, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure ++ this.cserver.getPluginManager().callEvent(playerJoinEvent); ++ ++ if (!player.connection.isAcceptingMessages()) { ++ return; ++ } ++ ++ final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); ++ ++ if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure ++ joinMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure ++ this.server.getPlayerList().broadcastSystemMessage(joinMessage, false); // Paper - Adventure ++ } ++ // CraftBukkit end ++ ++ // CraftBukkit start - sendAll above replaced with this loop ++ ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player ++ ++ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join ++ for (int i = 0; i < this.players.size(); ++i) { ++ ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); ++ ++ if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { ++ // Paper start - Add Listing API for Player ++ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { ++ // Paper end - Add Listing API for Player ++ entityplayer1.connection.send(packet); ++ // Paper start - Add Listing API for Player ++ } else { ++ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); ++ } ++ // Paper end - Add Listing API for Player ++ } ++ ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player ++ continue; ++ } ++ ++ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join ++ } ++ // Paper start - Use single player info update packet on join ++ if (!onlinePlayers.isEmpty()) { ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player ++ } ++ // Paper end - Use single player info update packet on join ++ player.sentListPacket = true; ++ player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready ++ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now ++ // CraftBukkit end ++ ++ //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE ++ ++ this.sendLevelInfo(player, worldserver1); ++ ++ // CraftBukkit start - Only add if the player wasn't moved in the event ++ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) { ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); ++ } ++ ++ worldserver1 = player.serverLevel(); // CraftBukkit - Update in case join event changed it ++ // CraftBukkit end ++ this.sendActivePlayerEffects(player); ++ // Paper - move loading pearls / parent vehicle up + player.initInventoryMenu(); ++ // CraftBukkit - Moved from above, added world ++ // Paper start - Configurable player collision; Add to collideRule team if needed ++ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName); ++ if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { ++ scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); ++ } ++ // Paper end - Configurable player collision ++ PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); ++ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead ++ if (player.isDeadOrDying()) { ++ net.minecraft.core.Holder plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) ++ .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( ++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), ++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null) ++ ); ++ } ++ // Paper end - Send empty chunk + } + + public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) { +@@ -269,30 +466,31 @@ + } + + public void addWorldborderListener(ServerLevel world) { ++ if (this.playerIo != null) return; // CraftBukkit + world.getWorldBorder().addListener(new BorderChangeListener() { + @Override + public void onBorderSizeSet(WorldBorder border, double size) { +- PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border)); ++ PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); // CraftBukkit + } + + @Override + public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) { +- PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border)); ++ PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); // CraftBukkit + } + + @Override + public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) { +- PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border)); ++ PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); // CraftBukkit + } + + @Override + public void onBorderSetWarningTime(WorldBorder border, int warningTime) { +- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border)); ++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); // CraftBukkit + } + + @Override + public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) { +- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border)); ++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); // CraftBukkit + } + + @Override +@@ -319,14 +517,15 @@ + } + + protected void save(ServerPlayer player) { ++ if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit + this.playerIo.save(player); +- ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) this.stats.get(player.getUUID()); ++ ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + + if (serverstatisticmanager != null) { + serverstatisticmanager.save(); + } + +- PlayerAdvancements advancementdataplayer = (PlayerAdvancements) this.advancements.get(player.getUUID()); ++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit + + if (advancementdataplayer != null) { + advancementdataplayer.save(); +@@ -334,95 +533,216 @@ + + } + +- public void remove(ServerPlayer player) { +- ServerLevel worldserver = player.serverLevel(); ++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component ++ // Paper start - Fix kick event leave message not being sent ++ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); ++ } ++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { ++ // Paper end - Fix kick event leave message not being sent ++ ServerLevel worldserver = entityplayer.serverLevel(); + +- player.awardStat(Stats.LEAVE_GAME); +- this.save(player); +- if (player.isPassenger()) { +- Entity entity = player.getRootVehicle(); ++ entityplayer.awardStat(Stats.LEAVE_GAME); + ++ // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it ++ // See SPIGOT-5799, SPIGOT-6145 ++ if (entityplayer.containerMenu != entityplayer.inventoryMenu) { ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason ++ } ++ ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - Adventure & Add API for quit reason ++ this.cserver.getPluginManager().callEvent(playerQuitEvent); ++ entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); ++ ++ entityplayer.doTick(); // SPIGOT-924 ++ // CraftBukkit end ++ ++ // Paper start - Configurable player collision; Remove from collideRule team if needed ++ if (this.collideRuleTeamName != null) { ++ final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); ++ if (entityplayer.getTeam() == team && team != null) { ++ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); ++ } ++ } ++ // Paper end - Configurable player collision ++ ++ // Paper - Drop carried item when player has disconnected ++ if (!entityplayer.containerMenu.getCarried().isEmpty()) { ++ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried(); ++ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY); ++ entityplayer.drop(carried, false); ++ } ++ // Paper end - Drop carried item when player has disconnected ++ ++ this.save(entityplayer); ++ if (entityplayer.isPassenger()) { ++ Entity entity = entityplayer.getRootVehicle(); ++ + if (entity.hasExactlyOnePlayerPassenger()) { + PlayerList.LOGGER.debug("Removing player mount"); +- player.stopRiding(); ++ entityplayer.stopRiding(); + entity.getPassengersAndSelf().forEach((entity1) -> { +- entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ // Paper start - Fix villager boat exploit ++ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { ++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ // Paper end - Fix villager boat exploit ++ entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause + }); + } + } + +- player.unRide(); +- Iterator iterator = player.getEnderPearls().iterator(); ++ entityplayer.unRide(); ++ Iterator iterator = entityplayer.getEnderPearls().iterator(); + + while (iterator.hasNext()) { + ThrownEnderpearl entityenderpearl = (ThrownEnderpearl) iterator.next(); + +- entityenderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ // Paper start - Allow using old ender pearl behavior ++ if (!entityenderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) { ++ entityenderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause ++ } else { ++ entityenderpearl.cachedOwner = null; ++ } ++ // Paper end - Allow using old ender pearl behavior + } + +- worldserver.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER); +- player.getAdvancements().stopListening(); +- this.players.remove(player); +- this.server.getCustomBossEvents().onPlayerDisconnect(player); +- UUID uuid = player.getUUID(); ++ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ entityplayer.retireScheduler(); // Paper - Folia schedulers ++ entityplayer.getAdvancements().stopListening(); ++ this.players.remove(entityplayer); ++ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot ++ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); ++ UUID uuid = entityplayer.getUUID(); + ServerPlayer entityplayer1 = (ServerPlayer) this.playersByUUID.get(uuid); + +- if (entityplayer1 == player) { ++ if (entityplayer1 == entityplayer) { + this.playersByUUID.remove(uuid); +- this.stats.remove(uuid); +- this.advancements.remove(uuid); ++ // CraftBukkit start ++ // this.stats.remove(uuid); ++ // this.advancements.remove(uuid); ++ // CraftBukkit end + } + +- this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()))); ++ // CraftBukkit start ++ // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID()))); ++ ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID())); ++ for (int i = 0; i < this.players.size(); i++) { ++ ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i); ++ ++ if (entityplayer2.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { ++ entityplayer2.connection.send(packet); ++ } else { ++ entityplayer2.getBukkitEntity().onEntityRemove(entityplayer); ++ } ++ } ++ // This removes the scoreboard (and player reference) for the specific player in the manager ++ this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); ++ // CraftBukkit end ++ ++ return playerQuitEvent.quitMessage(); // Paper - Adventure + } + +- @Nullable +- public Component canPlayerLogin(SocketAddress address, GameProfile profile) { ++ // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer ++ public ServerPlayer canPlayerLogin(ServerLoginPacketListenerImpl loginlistener, GameProfile gameprofile) { + MutableComponent ichatmutablecomponent; + +- if (this.bans.isBanned(profile)) { +- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(profile); ++ // Moved from processLogin ++ UUID uuid = gameprofile.getId(); ++ List list = Lists.newArrayList(); + ++ ServerPlayer entityplayer; ++ ++ for (int i = 0; i < this.players.size(); ++i) { ++ entityplayer = (ServerPlayer) this.players.get(i); ++ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames ++ list.add(entityplayer); ++ } ++ } ++ ++ Iterator iterator = list.iterator(); ++ ++ while (iterator.hasNext()) { ++ entityplayer = (ServerPlayer) iterator.next(); ++ this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved ++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause ++ } ++ ++ // 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 = loginlistener.connection.getRemoteAddress(); ++ ++ ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile, ClientInformation.createDefault()); ++ entity.transferCookieConnection = loginlistener; ++ Player player = entity.getBukkitEntity(); ++ PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); ++ ++ // Paper start - Fix MC-158900 ++ UserBanListEntry gameprofilebanentry; ++ if (this.bans.isBanned(gameprofile) && (gameprofilebanentry = this.bans.get(gameprofile)) != null) { ++ // Paper end - Fix MC-158900 ++ + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); + if (gameprofilebanentry.getExpires() != null) { + ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires()))); + } + +- return ichatmutablecomponent; +- } else if (!this.isWhiteListed(profile)) { +- return Component.translatable("multiplayer.disconnect.not_whitelisted"); +- } else if (this.ipBans.isBanned(address)) { +- IpBanListEntry ipbanentry = this.ipBans.get(address); ++ // return chatmessage; ++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure ++ } else if (!this.isWhiteListed(gameprofile, event)) { // Paper - ProfileWhitelistVerifyEvent ++ //ichatmutablecomponent = Component.translatable("multiplayer.disconnect.not_whitelisted"); // Paper ++ //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted ++ } else if (this.getIpBans().isBanned(socketaddress) && getIpBans().get(socketaddress) != null && !this.getIpBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans ++ IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); + if (ipbanentry.getExpires() != null) { + ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned_ip.expiration", PlayerList.BAN_DATE_FORMAT.format(ipbanentry.getExpires()))); + } + +- return ichatmutablecomponent; ++ // return chatmessage; ++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure + } else { +- return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(profile) ? Component.translatable("multiplayer.disconnect.server_full") : null; ++ // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; ++ if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { ++ event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure ++ } + } ++ ++ this.cserver.getPluginManager().callEvent(event); ++ if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { ++ loginlistener.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure ++ return null; ++ } ++ return entity; + } + +- public ServerPlayer getPlayerForLogin(GameProfile profile, ClientInformation syncedOptions) { +- return new ServerPlayer(this.server, this.server.overworld(), profile, syncedOptions); ++ // CraftBukkit start - added EntityPlayer ++ public ServerPlayer getPlayerForLogin(GameProfile gameprofile, ClientInformation clientinformation, ServerPlayer player) { ++ player.updateOptions(clientinformation); ++ return player; ++ // CraftBukkit end + } + +- public boolean disconnectAllPlayersWithProfile(GameProfile profile) { +- UUID uuid = profile.getId(); +- Set set = Sets.newIdentityHashSet(); ++ public boolean disconnectAllPlayersWithProfile(GameProfile gameprofile, ServerPlayer player) { // CraftBukkit - added EntityPlayer ++ /* CraftBukkit startMoved up ++ UUID uuid = gameprofile.getId(); ++ Set set = Sets.newIdentityHashSet(); + Iterator iterator = this.players.iterator(); + + while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); ++ EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + + if (entityplayer.getUUID().equals(uuid)) { + set.add(entityplayer); + } + } + +- ServerPlayer entityplayer1 = (ServerPlayer) this.playersByUUID.get(profile.getId()); ++ EntityPlayer entityplayer1 = (EntityPlayer) this.playersByUUID.get(gameprofile.getId()); + + if (entityplayer1 != null) { + set.add(entityplayer1); +@@ -431,72 +751,160 @@ + Iterator iterator1 = set.iterator(); + + while (iterator1.hasNext()) { +- ServerPlayer entityplayer2 = (ServerPlayer) iterator1.next(); ++ EntityPlayer entityplayer2 = (EntityPlayer) iterator1.next(); + + entityplayer2.connection.disconnect(PlayerList.DUPLICATE_LOGIN_DISCONNECT_MESSAGE); + } + + return !set.isEmpty(); ++ */ ++ return player == null; ++ // CraftBukkit end + } + +- public ServerPlayer respawn(ServerPlayer player, boolean alive, Entity.RemovalReason removalReason) { +- this.players.remove(player); +- player.serverLevel().removePlayerImmediately(player, removalReason); +- TeleportTransition teleporttransition = player.findRespawnPositionAndUseSpawnBlock(!alive, TeleportTransition.DO_NOTHING); +- ServerLevel worldserver = teleporttransition.newLevel(); +- ServerPlayer entityplayer1 = new ServerPlayer(this.server, worldserver, player.getGameProfile(), player.clientInformation()); ++ // CraftBukkit start ++ public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason) { ++ return this.respawn(entityplayer, flag, entity_removalreason, reason, null); ++ } + +- entityplayer1.connection = player.connection; +- entityplayer1.restoreFrom(player, alive); +- entityplayer1.setId(player.getId()); +- entityplayer1.setMainArm(player.getMainArm()); ++ public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason, Location location) { ++ entityplayer.stopRiding(); // CraftBukkit ++ this.players.remove(entityplayer); ++ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot ++ entityplayer.serverLevel().removePlayerImmediately(entityplayer, entity_removalreason); ++ /* CraftBukkit start ++ TeleportTransition teleporttransition = entityplayer.findRespawnPositionAndUseSpawnBlock(!flag, TeleportTransition.DO_NOTHING); ++ WorldServer worldserver = teleporttransition.newLevel(); ++ EntityPlayer entityplayer1 = new EntityPlayer(this.server, worldserver, entityplayer.getGameProfile(), entityplayer.clientInformation()); ++ // */ ++ ServerPlayer entityplayer1 = entityplayer; ++ Level fromWorld = entityplayer.level(); ++ entityplayer.wonGame = false; ++ // CraftBukkit end ++ ++ entityplayer1.connection = entityplayer.connection; ++ entityplayer1.restoreFrom(entityplayer, flag); ++ entityplayer1.setId(entityplayer.getId()); ++ entityplayer1.setMainArm(entityplayer.getMainArm()); ++ // CraftBukkit - not required, just copies old location into reused entity ++ /* + if (!teleporttransition.missingRespawnBlock()) { +- entityplayer1.copyRespawnPosition(player); ++ entityplayer1.copyRespawnPosition(entityplayer); + } ++ */ ++ // CraftBukkit end + +- Iterator iterator = player.getTags().iterator(); ++ Iterator iterator = entityplayer.getTags().iterator(); + + while (iterator.hasNext()) { + String s = (String) iterator.next(); + + entityplayer1.addTag(s); + } ++ // Paper start - Add PlayerPostRespawnEvent ++ boolean isBedSpawn = false; ++ boolean isRespawn = false; ++ // Paper end - Add PlayerPostRespawnEvent + ++ // CraftBukkit start - fire PlayerRespawnEvent ++ TeleportTransition teleporttransition; ++ if (location == null) { ++ teleporttransition = entityplayer.findRespawnPositionAndUseSpawnBlock(!flag, TeleportTransition.DO_NOTHING, reason); ++ ++ if (!flag) entityplayer.reset(); // SPIGOT-4785 ++ // Paper start - Add PlayerPostRespawnEvent ++ if (teleporttransition == null) return entityplayer; // Early exit, mirrors belows early return for disconnected players in respawn event ++ isRespawn = true; ++ location = CraftLocation.toBukkit(teleporttransition.position(), teleporttransition.newLevel().getWorld(), teleporttransition.yRot(), teleporttransition.xRot()); ++ // Paper end - Add PlayerPostRespawnEvent ++ } else { ++ teleporttransition = new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), TeleportTransition.DO_NOTHING); ++ } ++ // Spigot Start ++ if (teleporttransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event ++ return entityplayer; ++ } ++ // Spigot End ++ ServerLevel worldserver = teleporttransition.newLevel(); ++ entityplayer1.spawnIn(worldserver); ++ entityplayer1.unsetRemoved(); ++ entityplayer1.setShiftKeyDown(false); + Vec3 vec3d = teleporttransition.position(); + +- entityplayer1.moveTo(vec3d.x, vec3d.y, vec3d.z, teleporttransition.yRot(), teleporttransition.xRot()); ++ entityplayer1.forceSetPositionRotation(vec3d.x, vec3d.y, vec3d.z, teleporttransition.yRot(), teleporttransition.xRot()); ++ worldserver.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(vec3d.x()) >> 4, net.minecraft.util.Mth.floor(vec3d.z()) >> 4), 1, entityplayer.getId()); // Paper - post teleport ticket type ++ // CraftBukkit end + if (teleporttransition.missingRespawnBlock()) { + entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); ++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent + } + +- int i = alive ? 1 : 0; ++ int i = flag ? 1 : 0; + ServerLevel worldserver1 = entityplayer1.serverLevel(); + LevelData worlddata = worldserver1.getLevelData(); + + entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver1), (byte) i)); +- entityplayer1.connection.teleport(entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()); ++ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot ++ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot ++ entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit + entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle())); + entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); + entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel)); + this.sendActivePlayerEffects(entityplayer1); + this.sendLevelInfo(entityplayer1, worldserver); + this.sendPlayerPermissionLevel(entityplayer1); +- worldserver.addRespawnedPlayer(entityplayer1); +- this.players.add(entityplayer1); +- this.playersByUUID.put(entityplayer1.getUUID(), entityplayer1); +- entityplayer1.initInventoryMenu(); ++ if (!entityplayer.connection.isDisconnected()) { ++ worldserver.addRespawnedPlayer(entityplayer1); ++ this.players.add(entityplayer1); ++ this.playersByName.put(entityplayer1.getScoreboardName().toLowerCase(java.util.Locale.ROOT), entityplayer1); // Spigot ++ this.playersByUUID.put(entityplayer1.getUUID(), entityplayer1); ++ } ++ // entityplayer1.initInventoryMenu(); + entityplayer1.setHealth(entityplayer1.getHealth()); + BlockPos blockposition = entityplayer1.getRespawnPosition(); + ServerLevel worldserver2 = this.server.getLevel(entityplayer1.getRespawnDimension()); + +- if (!alive && blockposition != null && worldserver2 != null) { ++ if (!flag && blockposition != null && worldserver2 != null) { + BlockState iblockdata = worldserver2.getBlockState(blockposition); + + if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { + entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver.getRandom().nextLong())); + } ++ // Paper start - Add PlayerPostRespawnEvent ++ if (iblockdata.is(net.minecraft.tags.BlockTags.BEDS) && !teleporttransition.missingRespawnBlock()) { ++ isBedSpawn = true; ++ } ++ // Paper end - Add PlayerPostRespawnEvent + } ++ // Added from changeDimension ++ this.sendAllPlayerInfo(entityplayer); // Update health, etc... ++ entityplayer.onUpdateAbilities(); ++ for (MobEffectInstance mobEffect : entityplayer.getActiveEffects()) { ++ entityplayer.connection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobEffect, false)); // blend = false ++ } + ++ // Fire advancement trigger ++ entityplayer.triggerDimensionChangeTriggers(worldserver); ++ ++ // Don't fire on respawn ++ if (fromWorld != worldserver) { ++ PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(entityplayer.getBukkitEntity(), fromWorld.getWorld()); ++ this.server.server.getPluginManager().callEvent(event); ++ } ++ ++ // Save player file again if they were disconnected ++ if (entityplayer.connection.isDisconnected()) { ++ this.save(entityplayer); ++ } ++ ++ // Paper start - Add PlayerPostRespawnEvent ++ if (isRespawn) { ++ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn)); ++ } ++ // Paper end - Add PlayerPostRespawnEvent ++ ++ // CraftBukkit end ++ + return entityplayer1; + } + +@@ -505,26 +913,48 @@ + } + + public void sendActiveEffects(LivingEntity entity, ServerGamePacketListenerImpl networkHandler) { ++ // Paper start - collect packets ++ this.sendActiveEffects(entity, networkHandler::send); ++ } ++ public void sendActiveEffects(LivingEntity entity, java.util.function.Consumer> packetConsumer) { ++ // Paper end - collect packets + Iterator iterator = entity.getActiveEffects().iterator(); + + while (iterator.hasNext()) { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + +- networkHandler.send(new ClientboundUpdateMobEffectPacket(entity.getId(), mobeffect, false)); ++ packetConsumer.accept(new ClientboundUpdateMobEffectPacket(entity.getId(), mobeffect, false)); // Paper - collect packets + } + + } + + public void sendPlayerPermissionLevel(ServerPlayer player) { ++ // Paper start - avoid recalculating permissions if possible ++ this.sendPlayerPermissionLevel(player, true); ++ } ++ ++ public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) { ++ // Paper end - avoid recalculating permissions if possible + GameProfile gameprofile = player.getGameProfile(); + int i = this.server.getProfilePermissions(gameprofile); + +- this.sendPlayerPermissionLevel(player, i); ++ this.sendPlayerPermissionLevel(player, i, recalculatePermissions); // Paper - avoid recalculating permissions if possible + } + + public void tick() { + if (++this.sendAllPlayerInfoIn > 600) { +- this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players)); ++ // CraftBukkit start ++ for (int i = 0; i < this.players.size(); ++i) { ++ final ServerPlayer target = (ServerPlayer) this.players.get(i); ++ ++ target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players.stream().filter(new Predicate() { ++ @Override ++ public boolean test(ServerPlayer input) { ++ return target.getBukkitEntity().canSee(input.getBukkitEntity()); ++ } ++ }).collect(Collectors.toList()))); ++ } ++ // CraftBukkit end + this.sendAllPlayerInfoIn = 0; + } + +@@ -541,6 +971,25 @@ + + } + ++ // CraftBukkit start - add a world/entity limited version ++ public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) { ++ for (int i = 0; i < this.players.size(); ++i) { ++ ServerPlayer entityplayer = this.players.get(i); ++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) { ++ continue; ++ } ++ ((ServerPlayer) this.players.get(i)).connection.send(packet); ++ } ++ } ++ ++ public void broadcastAll(Packet packet, Level world) { ++ for (int i = 0; i < world.players().size(); ++i) { ++ ((ServerPlayer) world.players().get(i)).connection.send(packet); ++ } ++ ++ } ++ // CraftBukkit end ++ + public void broadcastAll(Packet packet, ResourceKey dimension) { + Iterator iterator = this.players.iterator(); + +@@ -554,7 +1003,7 @@ + + } + +- public void broadcastSystemToTeam(Player source, Component message) { ++ public void broadcastSystemToTeam(net.minecraft.world.entity.player.Player source, Component message) { + PlayerTeam scoreboardteam = source.getTeam(); + + if (scoreboardteam != null) { +@@ -573,7 +1022,7 @@ + } + } + +- public void broadcastSystemToAllExceptTeam(Player source, Component message) { ++ public void broadcastSystemToAllExceptTeam(net.minecraft.world.entity.player.Player source, Component message) { + PlayerTeam scoreboardteam = source.getTeam(); + + if (scoreboardteam == null) { +@@ -619,7 +1068,7 @@ + } + + public void deop(GameProfile profile) { +- this.ops.remove((Object) profile); ++ this.ops.remove(profile); // CraftBukkit - decompile error + ServerPlayer entityplayer = this.getPlayer(profile.getId()); + + if (entityplayer != null) { +@@ -629,6 +1078,11 @@ + } + + private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { ++ // Paper start - Add sendOpLevel API ++ this.sendPlayerPermissionLevel(player, permissionLevel, true); ++ } ++ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { ++ // Paper end - Add sendOpLevel API + if (player.connection != null) { + byte b0; + +@@ -643,36 +1097,53 @@ + player.connection.send(new ClientboundEntityEventPacket(player, b0)); + } + ++ if (recalculatePermissions) { // Paper - Add sendOpLevel API ++ player.getBukkitEntity().recalculatePermissions(); // CraftBukkit + this.server.getCommands().sendCommands(player); ++ } // Paper - Add sendOpLevel API + } + + public boolean isWhiteListed(GameProfile profile) { +- return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile); ++ // Paper start - ProfileWhitelistVerifyEvent ++ return this.isWhiteListed(profile, null); + } ++ public boolean isWhiteListed(GameProfile gameprofile, @Nullable org.bukkit.event.player.PlayerLoginEvent loginEvent) { ++ boolean isOp = this.ops.contains(gameprofile); ++ boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile); ++ final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; + ++ final net.kyori.adventure.text.Component configuredMessage = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage); ++ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile), this.doWhiteList, isWhitelisted, isOp, configuredMessage); ++ event.callEvent(); ++ if (!event.isWhitelisted()) { ++ if (loginEvent != null) { ++ loginEvent.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, event.kickMessage() == null ? configuredMessage : event.kickMessage()); ++ } ++ return false; ++ } ++ return true; ++ // Paper end - ProfileWhitelistVerifyEvent ++ } ++ + public boolean isOp(GameProfile profile) { + return this.ops.contains(profile) || this.server.isSingleplayerOwner(profile) && this.server.getWorldData().isAllowCommands() || this.allowCommandsForAllPlayers; + } + + @Nullable + public ServerPlayer getPlayerByName(String name) { +- int i = this.players.size(); +- +- for (int j = 0; j < i; ++j) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(j); +- +- if (entityplayer.getGameProfile().getName().equalsIgnoreCase(name)) { +- return entityplayer; +- } +- } +- +- return null; ++ return this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); // Spigot + } + +- public void broadcast(@Nullable Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { ++ public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer = (ServerPlayer) this.players.get(i); + ++ // CraftBukkit start - Test if player receiving packet can see the source of the packet ++ if (player != null && !entityplayer.getBukkitEntity().canSee(player.getBukkitEntity())) { ++ continue; ++ } ++ // CraftBukkit end ++ + if (entityplayer != player && entityplayer.level().dimension() == worldKey) { + double d4 = x - entityplayer.getX(); + double d5 = y - entityplayer.getY(); +@@ -687,10 +1158,12 @@ + } + + public void saveAll() { ++ io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + for (int i = 0; i < this.players.size(); ++i) { + this.save((ServerPlayer) this.players.get(i)); + } + ++ return null; }); // Paper - ensure main + } + + public UserWhiteList getWhiteList() { +@@ -712,15 +1185,19 @@ + public void reloadWhiteList() {} + + public void sendLevelInfo(ServerPlayer player, ServerLevel world) { +- WorldBorder worldborder = this.server.overworld().getWorldBorder(); ++ WorldBorder worldborder = player.level().getWorldBorder(); // CraftBukkit + + player.connection.send(new ClientboundInitializeBorderPacket(worldborder)); + player.connection.send(new ClientboundSetTimePacket(world.getGameTime(), world.getDayTime(), world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); + player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSharedSpawnPos(), world.getSharedSpawnAngle())); + if (world.isRaining()) { +- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F)); +- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, world.getRainLevel(1.0F))); +- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, world.getThunderLevel(1.0F))); ++ // CraftBukkit start - handle player weather ++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); ++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, worldserver.getRainLevel(1.0F))); ++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, worldserver.getThunderLevel(1.0F))); ++ player.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false); ++ player.updateWeather(-world.rainLevel, world.rainLevel, -world.thunderLevel, world.thunderLevel); ++ // CraftBukkit end + } + + player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F)); +@@ -729,8 +1206,16 @@ + + public void sendAllPlayerInfo(ServerPlayer player) { + player.inventoryMenu.sendAllDataToRemote(); +- player.resetSentInfo(); ++ // entityplayer.resetSentInfo(); ++ player.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange ++ player.refreshEntityData(player); // CraftBukkkit - SPIGOT-7218: sync metadata + player.connection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected)); ++ // CraftBukkit start - from GameRules ++ int i = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23; ++ player.connection.send(new ClientboundEntityEventPacket(player, (byte) i)); ++ float immediateRespawn = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN) ? 1.0F: 0.0F; ++ player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, immediateRespawn)); ++ // CraftBukkit end + } + + public int getPlayerCount() { +@@ -746,6 +1231,7 @@ + } + + public void setUsingWhiteList(boolean whitelistEnabled) { ++ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); // Paper - WhitelistToggleEvent + this.doWhiteList = whitelistEnabled; + } + +@@ -786,11 +1272,35 @@ + } + + public void removeAll() { +- for (int i = 0; i < this.players.size(); ++i) { +- ((ServerPlayer) this.players.get(i)).connection.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown")); ++ // Paper start - Extract method to allow for restarting flag ++ this.removeAll(false); ++ } ++ ++ public void removeAll(boolean isRestarting) { ++ // Paper end ++ // CraftBukkit start - disconnect safely ++ for (ServerPlayer player : this.players) { ++ if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) ++ player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure ++ } ++ // CraftBukkit end ++ ++ // Paper start - Configurable player collision; Remove collideRule team if it exists ++ if (this.collideRuleTeamName != null) { ++ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName); ++ if (team != null) scoreboard.removePlayerTeam(team); + } ++ // Paper end - Configurable player collision ++ } + ++ // CraftBukkit start ++ public void broadcastMessage(Component[] iChatBaseComponents) { ++ for (Component component : iChatBaseComponents) { ++ this.broadcastSystemMessage(component, false); ++ } + } ++ // CraftBukkit end + + public void broadcastSystemMessage(Component message, boolean overlay) { + this.broadcastSystemMessage(message, (entityplayer) -> { +@@ -819,24 +1329,43 @@ + } + + public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params) { ++ // Paper start ++ this.broadcastChatMessage(message, sender, params, null); ++ } ++ public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params, @Nullable Function unsignedFunction) { ++ // Paper end + Objects.requireNonNull(sender); +- this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, params); ++ this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, params, unsignedFunction); // Paper + } + + private void broadcastChatMessage(PlayerChatMessage message, Predicate shouldSendFiltered, @Nullable ServerPlayer sender, ChatType.Bound params) { ++ // Paper start ++ this.broadcastChatMessage(message, shouldSendFiltered, sender, params, null); ++ } ++ public void broadcastChatMessage(PlayerChatMessage message, Predicate shouldSendFiltered, @Nullable ServerPlayer sender, ChatType.Bound params, @Nullable Function unsignedFunction) { ++ // Paper end + boolean flag = this.verifyChatTrusted(message); + +- this.server.logChatMessage(message.decoratedContent(), params, flag ? null : "Not Secure"); ++ this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), params, flag ? null : "Not Secure"); // Paper + OutgoingChatMessage outgoingchatmessage = OutgoingChatMessage.create(message); + boolean flag1 = false; + + boolean flag2; ++ Packet disguised = sender != null && unsignedFunction == null ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(outgoingchatmessage.content(), params) : null; // Paper - don't send player chat packets from vanished players + + for (Iterator iterator = this.players.iterator(); iterator.hasNext(); flag1 |= flag2 && message.isFullyFiltered()) { + ServerPlayer entityplayer1 = (ServerPlayer) iterator.next(); + + flag2 = shouldSendFiltered.test(entityplayer1); +- entityplayer1.sendChatMessage(outgoingchatmessage, flag2, params); ++ // Paper start - don't send player chat packets from vanished players ++ if (sender != null && !entityplayer1.getBukkitEntity().canSee(sender.getBukkitEntity())) { ++ entityplayer1.connection.send(unsignedFunction != null ++ ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(unsignedFunction.apply(entityplayer1.getBukkitEntity()), params) ++ : disguised); ++ continue; ++ } ++ // Paper end ++ entityplayer1.sendChatMessage(outgoingchatmessage, flag2, params, unsignedFunction == null ? null : unsignedFunction.apply(entityplayer1.getBukkitEntity())); // Paper + } + + if (flag1 && sender != null) { +@@ -845,20 +1374,27 @@ + + } + +- private boolean verifyChatTrusted(PlayerChatMessage message) { ++ public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public + return message.hasSignature() && !message.hasExpiredServer(Instant.now()); + } + +- public ServerStatsCounter getPlayerStats(Player player) { +- UUID uuid = player.getUUID(); +- ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) this.stats.get(uuid); ++ // CraftBukkit start ++ public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { ++ ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); ++ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name ++ } + ++ public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { ++ ServerPlayer entityhuman = this.getPlayer(uuid); ++ ServerStatsCounter serverstatisticmanager = entityhuman == null ? null : (ServerStatsCounter) entityhuman.getStats(); ++ // CraftBukkit end ++ + if (serverstatisticmanager == null) { + File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile(); + File file1 = new File(file, String.valueOf(uuid) + ".json"); + + if (!file1.exists()) { +- File file2 = new File(file, player.getName().getString() + ".json"); ++ File file2 = new File(file, displayName + ".json"); // CraftBukkit + Path path = file2.toPath(); + + if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file2.isFile()) { +@@ -867,7 +1403,7 @@ + } + + serverstatisticmanager = new ServerStatsCounter(this.server, file1); +- this.stats.put(uuid, serverstatisticmanager); ++ // this.stats.put(uuid, serverstatisticmanager); // CraftBukkit + } + + return serverstatisticmanager; +@@ -875,13 +1411,13 @@ + + public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) { + UUID uuid = player.getUUID(); +- PlayerAdvancements advancementdataplayer = (PlayerAdvancements) this.advancements.get(uuid); ++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit + + if (advancementdataplayer == null) { + Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(String.valueOf(uuid) + ".json"); + + advancementdataplayer = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player); +- this.advancements.put(uuid, advancementdataplayer); ++ // this.advancements.put(uuid, advancementdataplayer); // CraftBukkit + } + + advancementdataplayer.setPlayer(player); +@@ -932,15 +1468,39 @@ + } + + public void reloadResources() { +- Iterator iterator = this.advancements.values().iterator(); ++ // Paper start - API for updating recipes on clients ++ this.reloadAdvancementData(); ++ this.reloadTagData(); ++ this.reloadRecipes(); ++ } ++ public void reloadAdvancementData() { ++ // Paper end - API for updating recipes on clients ++ // CraftBukkit start ++ /*Iterator iterator = this.advancements.values().iterator(); + + while (iterator.hasNext()) { +- PlayerAdvancements advancementdataplayer = (PlayerAdvancements) iterator.next(); ++ AdvancementDataPlayer advancementdataplayer = (AdvancementDataPlayer) iterator.next(); + + advancementdataplayer.reload(this.server.getAdvancements()); ++ }*/ ++ ++ for (ServerPlayer player : this.players) { ++ player.getAdvancements().reload(this.server.getAdvancements()); ++ player.getAdvancements().flushDirty(player); // CraftBukkit - trigger immediate flush of advancements + } ++ // CraftBukkit end + ++ // Paper start - API for updating recipes on clients ++ } ++ public void reloadTagData() { + this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); ++ // CraftBukkit start ++ // this.reloadRecipes(); // Paper - do not reload recipes just because tag data was reloaded ++ // Paper end - API for updating recipes on clients ++ } ++ ++ public void reloadRecipes() { ++ // CraftBukkit end + RecipeManager craftingmanager = this.server.getRecipeManager(); + ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes()); + Iterator iterator1 = this.players.iterator(); diff --git a/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch new file mode 100644 index 0000000000..568224fe25 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/server/players/SleepStatus.java ++++ b/net/minecraft/server/players/SleepStatus.java +@@ -18,9 +18,12 @@ + } + + public boolean areEnoughDeepSleeping(int percentage, List players) { +- int j = (int) players.stream().filter(Player::isSleepingLongEnough).count(); ++ // CraftBukkit start ++ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); ++ boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); + +- return j >= this.sleepersNeeded(percentage); ++ return anyDeepSleep && j >= this.sleepersNeeded(percentage); ++ // CraftBukkit end + } + + public int sleepersNeeded(int percentage) { +@@ -42,18 +45,24 @@ + this.activePlayers = 0; + this.sleepingPlayers = 0; + Iterator iterator = players.iterator(); ++ boolean anySleep = false; // CraftBukkit + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + + if (!entityplayer.isSpectator()) { + ++this.activePlayers; +- if (entityplayer.isSleeping()) { ++ if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit + ++this.sleepingPlayers; + } ++ // CraftBukkit start ++ if (entityplayer.isSleeping()) { ++ anySleep = true; ++ } ++ // CraftBukkit end + } + } + +- return (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); ++ return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch new file mode 100644 index 0000000000..bea194db95 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/server/players/StoredUserList.java ++++ b/net/minecraft/server/players/StoredUserList.java +@@ -30,7 +30,7 @@ + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); + private final File file; +- private final Map map = Maps.newHashMap(); ++ private final Map map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList + + public StoredUserList(File file) { + this.file = file; +@@ -53,8 +53,11 @@ + + @Nullable + public V get(K key) { +- this.removeExpired(); +- return (StoredUserEntry) this.map.get(this.getKeyForUser(key)); ++ // Paper start - Use ConcurrentHashMap in JsonList ++ return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> { ++ return v.hasExpired() ? null : v; ++ }); ++ // Paper end - Use ConcurrentHashMap in JsonList + } + + public void remove(K key) { +@@ -77,7 +80,7 @@ + } + + public boolean isEmpty() { +- return this.map.size() < 1; ++ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList + } + + protected String getKeyForUser(K profile) { +@@ -85,29 +88,12 @@ + } + + protected boolean contains(K k0) { ++ this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...) + return this.map.containsKey(this.getKeyForUser(k0)); + } + + private void removeExpired() { +- List list = Lists.newArrayList(); +- Iterator iterator = this.map.values().iterator(); +- +- while (iterator.hasNext()) { +- V v0 = (StoredUserEntry) iterator.next(); +- +- if (v0.hasExpired()) { +- list.add(v0.getUser()); +- } +- } +- +- iterator = list.iterator(); +- +- while (iterator.hasNext()) { +- K k0 = iterator.next(); +- +- this.map.remove(this.getKeyForUser(k0)); +- } +- ++ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList + } + + protected abstract StoredUserEntry createEntry(JsonObject json); +@@ -117,8 +103,9 @@ + } + + public void save() throws IOException { ++ this.removeExpired(); // Paper - remove expired values before saving + JsonArray jsonarray = new JsonArray(); +- Stream stream = this.map.values().stream().map((jsonlistentry) -> { ++ Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error + JsonObject jsonobject = new JsonObject(); + + Objects.requireNonNull(jsonlistentry); +@@ -171,9 +158,17 @@ + StoredUserEntry jsonlistentry = this.createEntry(jsonobject); + + if (jsonlistentry.getUser() != null) { +- this.map.put(this.getKeyForUser(jsonlistentry.getUser()), jsonlistentry); ++ this.map.put(this.getKeyForUser(jsonlistentry.getUser()), (V) jsonlistentry); // CraftBukkit - decompile error + } + } ++ // Spigot Start ++ } catch ( com.google.gson.JsonParseException | NullPointerException ex ) ++ { ++ org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex ); ++ File backup = new File( this.file + ".backup" ); ++ this.file.renameTo( backup ); ++ this.file.delete(); ++ // Spigot End + } catch (Throwable throwable) { + if (bufferedreader != null) { + try { diff --git a/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch new file mode 100644 index 0000000000..df038411ce --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/server/players/UserBanListEntry.java ++++ b/net/minecraft/server/players/UserBanListEntry.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.server.players; + + import com.google.gson.JsonObject; +@@ -39,20 +40,29 @@ + + @Nullable + private static GameProfile createGameProfile(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 { + uuid = UUID.fromString(s); + } catch (Throwable throwable) { +- 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/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch new file mode 100644 index 0000000000..4cef2c2bc4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/server/players/UserWhiteList.java ++++ b/net/minecraft/server/players/UserWhiteList.java +@@ -28,4 +28,23 @@ + protected String getKeyForUser(GameProfile gameProfile) { + return gameProfile.getId().toString(); + } ++ // Paper start - Add whitelist events ++ @Override ++ public void add(UserWhiteListEntry entry) { ++ if (!new io.papermc.paper.event.server.WhitelistStateUpdateEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(entry.getUser()), io.papermc.paper.event.server.WhitelistStateUpdateEvent.WhitelistStatus.ADDED).callEvent()) { ++ return; ++ } ++ ++ super.add(entry); ++ } ++ ++ @Override ++ public void remove(GameProfile profile) { ++ if (!new io.papermc.paper.event.server.WhitelistStateUpdateEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitCopy(profile), io.papermc.paper.event.server.WhitelistStateUpdateEvent.WhitelistStatus.REMOVED).callEvent()) { ++ return; ++ } ++ ++ super.remove(profile); ++ } ++ // Paper end - Add whitelist events + } diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch new file mode 100644 index 0000000000..589c29e384 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/server/rcon/RconConsoleSource.java ++++ b/net/minecraft/server/rcon/RconConsoleSource.java +@@ -8,16 +8,24 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; +- ++// CraftBukkit start ++import java.net.SocketAddress; ++import org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender; ++// CraftBukkit end + public class RconConsoleSource implements CommandSource { + + private static final String RCON = "Rcon"; + private static final Component RCON_COMPONENT = Component.literal("Rcon"); + private final StringBuffer buffer = new StringBuffer(); + private final MinecraftServer server; ++ // CraftBukkit start ++ public final SocketAddress socketAddress; ++ private final CraftRemoteConsoleCommandSender remoteConsole = new CraftRemoteConsoleCommandSender(this); + +- public RconConsoleSource(MinecraftServer server) { +- this.server = server; ++ public RconConsoleSource(MinecraftServer minecraftserver, SocketAddress socketAddress) { ++ this.socketAddress = socketAddress; ++ // CraftBukkit end ++ this.server = minecraftserver; + } + + public void prepareForCommand() { +@@ -34,7 +42,18 @@ + return new CommandSourceStack(this, Vec3.atLowerCornerOf(worldserver.getSharedSpawnPos()), Vec2.ZERO, worldserver, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null); + } + ++ // CraftBukkit start - Send a String ++ public void sendMessage(String message) { ++ this.buffer.append(message); ++ } ++ + @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return this.remoteConsole; ++ } ++ // CraftBukkit end ++ ++ @Override + public void sendSystemMessage(Component message) { + this.buffer.append(message.getString()); + } diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch new file mode 100644 index 0000000000..6a8d04320c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch @@ -0,0 +1,126 @@ +--- a/net/minecraft/server/rcon/thread/QueryThreadGs4.java ++++ b/net/minecraft/server/rcon/thread/QueryThreadGs4.java +@@ -106,13 +106,32 @@ + NetworkDataOutputStream networkDataOutputStream = new NetworkDataOutputStream(1460); + networkDataOutputStream.write(0); + networkDataOutputStream.writeBytes(this.getIdentBytes(packet.getSocketAddress())); +- networkDataOutputStream.writeString(this.serverName); ++ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC; ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.serverName) ++ .map(this.worldName) ++ .currentPlayers(this.serverInterface.getPlayerCount()) ++ .maxPlayers(this.maxPlayers) ++ .port(this.serverPort) ++ .hostname(this.hostIp) ++ .gameVersion(this.serverInterface.getServerVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); ++ ++ networkDataOutputStream.writeString(queryResponse.getMotd()); + networkDataOutputStream.writeString("SMP"); +- networkDataOutputStream.writeString(this.worldName); +- networkDataOutputStream.writeString(Integer.toString(this.serverInterface.getPlayerCount())); +- networkDataOutputStream.writeString(Integer.toString(this.maxPlayers)); +- networkDataOutputStream.writeShort((short)this.serverPort); +- networkDataOutputStream.writeString(this.hostIp); ++ networkDataOutputStream.writeString(queryResponse.getMap()); ++ networkDataOutputStream.writeString(Integer.toString(queryResponse.getCurrentPlayers())); ++ networkDataOutputStream.writeString(Integer.toString(queryResponse.getMaxPlayers())); ++ networkDataOutputStream.writeShort((short) queryResponse.getPort()); ++ networkDataOutputStream.writeString(queryResponse.getHostname()); ++ // Paper end + this.sendTo(networkDataOutputStream.toByteArray(), packet); + LOGGER.debug("Status [{}]", socketAddress); + } +@@ -147,31 +166,75 @@ + this.rulesResponse.writeString("splitnum"); + this.rulesResponse.write(128); + this.rulesResponse.write(0); ++ // Paper start ++ // Pack plugins ++ java.util.List plugins = java.util.Collections.emptyList(); ++ org.bukkit.plugin.Plugin[] bukkitPlugins; ++ if (((net.minecraft.server.dedicated.DedicatedServer) this.serverInterface).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) { ++ plugins = java.util.stream.Stream.of(bukkitPlugins) ++ .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion())) ++ .collect(java.util.stream.Collectors.toList()); ++ } ++ ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder() ++ .motd(this.serverName) ++ .map(this.worldName) ++ .currentPlayers(this.serverInterface.getPlayerCount()) ++ .maxPlayers(this.maxPlayers) ++ .port(this.serverPort) ++ .hostname(this.hostIp) ++ .plugins(plugins) ++ .players(this.serverInterface.getPlayerNames()) ++ .gameVersion(this.serverInterface.getServerVersion()) ++ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion()) ++ .build(); ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType = ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL; ++ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent = ++ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse); ++ queryEvent.callEvent(); ++ queryResponse = queryEvent.getResponse(); + this.rulesResponse.writeString("hostname"); +- this.rulesResponse.writeString(this.serverName); ++ this.rulesResponse.writeString(queryResponse.getMotd()); + this.rulesResponse.writeString("gametype"); + this.rulesResponse.writeString("SMP"); + this.rulesResponse.writeString("game_id"); + this.rulesResponse.writeString("MINECRAFT"); + this.rulesResponse.writeString("version"); +- this.rulesResponse.writeString(this.serverInterface.getServerVersion()); ++ this.rulesResponse.writeString(queryResponse.getGameVersion()); + this.rulesResponse.writeString("plugins"); +- this.rulesResponse.writeString(this.serverInterface.getPluginNames()); ++ java.lang.StringBuilder pluginsString = new java.lang.StringBuilder(); ++ pluginsString.append(queryResponse.getServerVersion()); ++ if (!queryResponse.getPlugins().isEmpty()) { ++ pluginsString.append(": "); ++ java.util.Iterator iter = queryResponse.getPlugins().iterator(); ++ while (iter.hasNext()) { ++ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next(); ++ pluginsString.append(info.getName()); ++ if (info.getVersion() != null) { ++ pluginsString.append(' ').append(info.getVersion().replace(";", ",")); ++ } ++ if (iter.hasNext()) { ++ pluginsString.append(';').append(' '); ++ } ++ } ++ } ++ this.rulesResponse.writeString(pluginsString.toString()); + this.rulesResponse.writeString("map"); +- this.rulesResponse.writeString(this.worldName); ++ this.rulesResponse.writeString(queryResponse.getMap()); + this.rulesResponse.writeString("numplayers"); +- this.rulesResponse.writeString(this.serverInterface.getPlayerCount() + ""); ++ this.rulesResponse.writeString(Integer.toString(queryResponse.getCurrentPlayers())); + this.rulesResponse.writeString("maxplayers"); +- this.rulesResponse.writeString(this.maxPlayers + ""); ++ this.rulesResponse.writeString(Integer.toString(queryResponse.getMaxPlayers())); + this.rulesResponse.writeString("hostport"); +- this.rulesResponse.writeString(this.serverPort + ""); ++ this.rulesResponse.writeString(Integer.toString(queryResponse.getPort())); + this.rulesResponse.writeString("hostip"); +- this.rulesResponse.writeString(this.hostIp); ++ this.rulesResponse.writeString(queryResponse.getHostname()); + this.rulesResponse.write(0); + this.rulesResponse.write(1); + this.rulesResponse.writeString("player_"); + this.rulesResponse.write(0); +- String[] strings = this.serverInterface.getPlayerNames(); ++ String[] strings = queryResponse.getPlayers().toArray(String[]::new); + + for (String string : strings) { + this.rulesResponse.writeString(string); diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch new file mode 100644 index 0000000000..1aa09ff9d7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch @@ -0,0 +1,80 @@ +--- a/net/minecraft/server/rcon/thread/RconClient.java ++++ b/net/minecraft/server/rcon/thread/RconClient.java +@@ -8,9 +8,12 @@ + import java.net.Socket; + import java.nio.charset.StandardCharsets; + import java.util.Locale; ++import org.slf4j.Logger; + import net.minecraft.server.ServerInterface; ++// CraftBukkit start ++import net.minecraft.server.dedicated.DedicatedServer; + import net.minecraft.server.rcon.PktUtils; +-import org.slf4j.Logger; ++import net.minecraft.server.rcon.RconConsoleSource; + + public class RconClient extends GenericThread { + +@@ -24,11 +27,14 @@ + private final Socket client; + private final byte[] buf = new byte[1460]; + private final String rconPassword; +- private final ServerInterface serverInterface; ++ // CraftBukkit start ++ private final DedicatedServer serverInterface; ++ private final RconConsoleSource rconConsoleSource; ++ // CraftBukkit end + + RconClient(ServerInterface server, String password, Socket socket) { + super("RCON Client " + String.valueOf(socket.getInetAddress())); +- this.serverInterface = server; ++ this.serverInterface = (DedicatedServer) server; // CraftBukkit + this.client = socket; + + try { +@@ -38,11 +44,14 @@ + } + + this.rconPassword = password; ++ this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, socket.getRemoteSocketAddress()); // CraftBukkit + } + + public void run() { +- while (true) { +- try { ++ // CraftBukkit start - decompile error: switch try / while statement ++ try { ++ while (true) { ++ // CraftBukkit end + if (!this.running) { + return; + } +@@ -71,7 +80,7 @@ + String s = PktUtils.stringFromByteArray(this.buf, j, i); + + try { +- this.sendCmdResponse(l, this.serverInterface.runCommand(s)); ++ this.sendCmdResponse(l, this.serverInterface.runCommand(this.rconConsoleSource, s)); // CraftBukkit + } catch (Exception exception) { + this.sendCmdResponse(l, "Error executing: " + s + " (" + exception.getMessage() + ")"); + } +@@ -98,6 +107,7 @@ + continue; + } + } ++ } // CraftBukkit - decompile error: switch try / while statement + } catch (IOException ioexception) { + return; + } catch (Exception exception1) { +@@ -109,8 +119,10 @@ + this.running = false; + } + +- return; +- } ++ // CraftBukkit start - decompile error: switch try / while statement ++ // return; ++ // } ++ // CraftBukkit end + } + + private void send(int sessionToken, int responseType, String message) throws IOException { diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch new file mode 100644 index 0000000000..5bc2ff1f90 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/server/rcon/thread/RconThread.java ++++ b/net/minecraft/server/rcon/thread/RconThread.java +@@ -57,7 +57,7 @@ + @Nullable + public static RconThread create(ServerInterface server) { + DedicatedServerProperties dedicatedServerProperties = server.getProperties(); +- String string = server.getServerIp(); ++ String string = dedicatedServerProperties.rconIp; // Paper - Configurable rcon ip + if (string.isEmpty()) { + string = "0.0.0.0"; + } +@@ -104,6 +104,14 @@ + + this.clients.clear(); + } ++ // Paper start - don't wait for remote connections ++ public void stopNonBlocking() { ++ this.running = false; ++ for (RconClient client : this.clients) { ++ client.running = false; ++ } ++ } ++ // Paper end - don't wait for remote connections + + private void closeSocket(ServerSocket socket) { + LOGGER.debug("closeSocket: {}", socket); diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch new file mode 100644 index 0000000000..f95e98f66a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/stats/ServerRecipeBook.java ++++ b/net/minecraft/stats/ServerRecipeBook.java +@@ -29,6 +29,8 @@ + import net.minecraft.world.item.crafting.display.RecipeDisplayId; + import org.slf4j.Logger; + ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++ + public class ServerRecipeBook extends RecipeBook { + + public static final String RECIPE_BOOK_TAG = "recipeBook"; +@@ -72,7 +74,7 @@ + RecipeHolder recipeholder = (RecipeHolder) iterator.next(); + ResourceKey> resourcekey = recipeholder.id(); + +- if (!this.known.contains(resourcekey) && !recipeholder.value().isSpecial()) { ++ if (!this.known.contains(resourcekey) && !recipeholder.value().isSpecial() && CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, resourcekey.location())) { // CraftBukkit + this.add(resourcekey); + this.addHighlight(resourcekey); + this.displayResolver.displaysForRecipe(resourcekey, (recipedisplayentry) -> { +@@ -82,7 +84,7 @@ + } + } + +- if (!list.isEmpty()) { ++ if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent + player.connection.send(new ClientboundRecipeBookAddPacket(list, false)); + } + +@@ -105,7 +107,7 @@ + } + } + +- if (!list.isEmpty()) { ++ if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent + player.connection.send(new ClientboundRecipeBookRemovePacket(list)); + } + diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch new file mode 100644 index 0000000000..f82072300e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/stats/ServerStatsCounter.java ++++ b/net/minecraft/stats/ServerStatsCounter.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.stats; + + import com.google.common.collect.Maps; +@@ -57,9 +58,22 @@ + } + } + ++ // Paper start - Moved after stat fetching for player state file ++ // Moves the loading after vanilla loading, so it overrides the values. ++ // Disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving) ++ // Fixes stat initialization to not cause a NullPointerException ++ // Spigot start ++ for ( Map.Entry entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() ) ++ { ++ Stat wrapper = Stats.CUSTOM.get(java.util.Objects.requireNonNull(BuiltInRegistries.CUSTOM_STAT.getValue(entry.getKey()))); // Paper - ensured by SpigotConfig#stats ++ this.stats.put( wrapper, entry.getValue().intValue() ); ++ } ++ // Spigot end ++ // Paper end - Moved after stat fetching for player state file + } + + public void save() { ++ if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot + try { + FileUtils.writeStringToFile(this.file, this.toJson()); + } catch (IOException ioexception) { +@@ -70,6 +84,8 @@ + + @Override + public void setValue(Player player, Stat stat, int value) { ++ if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot ++ if (stat.getType() == Stats.CUSTOM && stat.getValue() instanceof final ResourceLocation resourceLocation && org.spigotmc.SpigotConfig.forcedStats.get(resourceLocation) != null) return; // Paper - disable saving forced stats + super.setValue(player, stat, value); + this.dirty.add(stat); + } +@@ -158,13 +174,12 @@ + } + + private Optional> getStat(StatType type, String id) { +- Optional optional = Optional.ofNullable(ResourceLocation.tryParse(id)); +- Registry iregistry = type.getRegistry(); ++ // CraftBukkit - decompile error start ++ Optional optional = Optional.ofNullable(ResourceLocation.tryParse(id)); ++ Registry iregistry = type.getRegistry(); + +- Objects.requireNonNull(iregistry); +- optional = optional.flatMap(iregistry::getOptional); +- Objects.requireNonNull(type); +- return optional.map(type::get); ++ return optional.flatMap(iregistry::getOptional).map(type::get); ++ // CraftBukkit - decompile error end + } + + private static CompoundTag fromJson(JsonObject json) { diff --git a/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch new file mode 100644 index 0000000000..51c6680579 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/stats/StatsCounter.java ++++ b/net/minecraft/stats/StatsCounter.java +@@ -16,6 +16,12 @@ + public void increment(Player player, Stat stat, int value) { + int j = (int) Math.min((long) this.getValue(stat) + (long) value, 2147483647L); + ++ // CraftBukkit start - fire Statistic events ++ org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), j); ++ if (cancellable != null && cancellable.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.setValue(player, stat, j); + } + diff --git a/paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch b/paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch new file mode 100644 index 0000000000..10e1ff5cb3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch @@ -0,0 +1,66 @@ +--- a/net/minecraft/tags/TagLoader.java ++++ b/net/minecraft/tags/TagLoader.java +@@ -86,7 +86,10 @@ + return list.isEmpty() ? Either.right(List.copyOf(sequencedSet)) : Either.left(list); + } + +- public Map> build(Map> tags) { ++ // Paper start - fire tag registrar events ++ public Map> build(Map> tags, @Nullable io.papermc.paper.tag.TagEventConfig eventConfig) { ++ tags = io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.firePreFlattenEvent(tags, eventConfig); ++ // Paper end - fire tag registrar event + final Map> map = new HashMap<>(); + TagEntry.Lookup lookup = new TagEntry.Lookup() { + @Nullable +@@ -114,7 +117,7 @@ + ) + .ifRight(values -> map.put(id, (List)values)) + ); +- return map; ++ return io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.firePostFlattenEvent(map, eventConfig); // Paper - fire tag registrar events + } + + public static void loadTagsFromNetwork(TagNetworkSerialization.NetworkPayload tags, WritableRegistry registry) { +@@ -122,28 +125,38 @@ + } + + public static List> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryManager) { ++ // Paper start - tag lifecycle - add cause ++ return loadTagsForExistingRegistries(resourceManager, registryManager, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); ++ } ++ public static List> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryManager, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) { ++ // Paper end - tag lifecycle - add cause + return registryManager.registries() +- .map(registry -> loadPendingTags(resourceManager, registry.value())) ++ .map(registry -> loadPendingTags(resourceManager, registry.value(), cause)) // Paper - tag lifecycle - add cause + .flatMap(Optional::stream) + .collect(Collectors.toUnmodifiableList()); + } + + public static void loadTagsForRegistry(ResourceManager resourceManager, WritableRegistry registry) { ++ // Paper start - tag lifecycle - add registrar event cause ++ loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); ++ } ++ public static void loadTagsForRegistry(ResourceManager resourceManager, WritableRegistry registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) { ++ // Paper end - tag lifecycle - add registrar event cause + ResourceKey> resourceKey = registry.key(); + TagLoader> tagLoader = new TagLoader<>(TagLoader.ElementLookup.fromWritableRegistry(registry), Registries.tagsDirPath(resourceKey)); +- tagLoader.build(tagLoader.load(resourceManager)).forEach((id, entries) -> registry.bindTag(TagKey.create(resourceKey, id), (List>)entries)); ++ tagLoader.build(tagLoader.load(resourceManager), io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.createEventConfig(registry, cause)).forEach((id, entries) -> registry.bindTag(TagKey.create(resourceKey, id), (List>)entries)); // Paper - tag lifecycle - add registrar event cause + } + + private static Map, List>> wrapTags(ResourceKey> registryRef, Map>> tags) { + return tags.entrySet().stream().collect(Collectors.toUnmodifiableMap(entry -> TagKey.create(registryRef, entry.getKey()), Entry::getValue)); + } + +- private static Optional> loadPendingTags(ResourceManager resourceManager, Registry registry) { ++ private static Optional> loadPendingTags(ResourceManager resourceManager, Registry registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) { // Paper - add registrar event cause + ResourceKey> resourceKey = registry.key(); + TagLoader> tagLoader = new TagLoader<>( + (TagLoader.ElementLookup>)TagLoader.ElementLookup.fromFrozenRegistry(registry), Registries.tagsDirPath(resourceKey) + ); +- TagLoader.LoadResult loadResult = new TagLoader.LoadResult<>(resourceKey, wrapTags(registry.key(), tagLoader.build(tagLoader.load(resourceManager)))); ++ TagLoader.LoadResult loadResult = new TagLoader.LoadResult<>(resourceKey, wrapTags(registry.key(), tagLoader.build(tagLoader.load(resourceManager), io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.createEventConfig(registry, cause)))); // Paper - add registrar event cause + return loadResult.tags().isEmpty() ? Optional.empty() : Optional.of(registry.prepareTagReload(loadResult)); + } + diff --git a/paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch b/paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch new file mode 100644 index 0000000000..d3a669c38d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch @@ -0,0 +1,70 @@ +--- a/net/minecraft/util/SimpleBitStorage.java ++++ b/net/minecraft/util/SimpleBitStorage.java +@@ -204,8 +204,8 @@ + private final long mask; + private final int size; + private final int valuesPerLong; +- private final int divideMul; +- private final int divideAdd; ++ private final int divideMul; private final long divideMulUnsigned; // Paper - Perf: Optimize SimpleBitStorage; referenced in b(int) with 2 Integer.toUnsignedLong calls ++ private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage + private final int divideShift; + + public SimpleBitStorage(int elementBits, int size, int[] data) { +@@ -248,8 +248,8 @@ + this.mask = (1L << elementBits) - 1L; + this.valuesPerLong = (char)(64 / elementBits); + int i = 3 * (this.valuesPerLong - 1); +- this.divideMul = MAGIC[i + 0]; +- this.divideAdd = MAGIC[i + 1]; ++ this.divideMul = MAGIC[i + 0]; this.divideMulUnsigned = Integer.toUnsignedLong(this.divideMul); // Paper - Perf: Optimize SimpleBitStorage ++ this.divideAdd = MAGIC[i + 1]; this.divideAddUnsigned = Integer.toUnsignedLong(this.divideAdd); // Paper - Perf: Optimize SimpleBitStorage + this.divideShift = MAGIC[i + 2]; + int j = (size + this.valuesPerLong - 1) / this.valuesPerLong; + if (data != null) { +@@ -264,15 +264,15 @@ + } + + private int cellIndex(int index) { +- long l = Integer.toUnsignedLong(this.divideMul); +- long m = Integer.toUnsignedLong(this.divideAdd); +- return (int)((long)index * l + m >> 32 >> this.divideShift); ++ //long l = Integer.toUnsignedLong(this.divideMul); // Paper - Perf: Optimize SimpleBitStorage ++ //long m = Integer.toUnsignedLong(this.divideAdd); // Paper - Perf: Optimize SimpleBitStorage ++ return (int) (index * this.divideMulUnsigned + this.divideAddUnsigned >> 32 >> this.divideShift); // Paper - Perf: Optimize SimpleBitStorage + } + + @Override +- public int getAndSet(int index, int value) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); +- Validate.inclusiveBetween(0L, this.mask, (long)value); ++ public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage + int i = this.cellIndex(index); + long l = this.data[i]; + int j = (index - i * this.valuesPerLong) * this.bits; +@@ -282,9 +282,9 @@ + } + + @Override +- public void set(int index, int value) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); +- Validate.inclusiveBetween(0L, this.mask, (long)value); ++ public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage + int i = this.cellIndex(index); + long l = this.data[i]; + int j = (index - i * this.valuesPerLong) * this.bits; +@@ -292,8 +292,8 @@ + } + + @Override +- public int get(int index) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); ++ public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage + int i = this.cellIndex(index); + long l = this.data[i]; + int j = (index - i * this.valuesPerLong) * this.bits; diff --git a/paper-server/patches/sources/net/minecraft/util/SortedArraySet.java.patch b/paper-server/patches/sources/net/minecraft/util/SortedArraySet.java.patch new file mode 100644 index 0000000000..00af73f775 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/SortedArraySet.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/util/SortedArraySet.java ++++ b/net/minecraft/util/SortedArraySet.java +@@ -28,7 +28,7 @@ + } + + public static > SortedArraySet create(int initialCapacity) { +- return new SortedArraySet<>(initialCapacity, Comparator.naturalOrder()); ++ return new SortedArraySet<>(initialCapacity, Comparator.naturalOrder()); // Paper - decompile fix + } + + public static SortedArraySet create(Comparator comparator) { diff --git a/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch new file mode 100644 index 0000000000..803fd149f1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/util/SpawnUtil.java ++++ b/net/minecraft/util/SpawnUtil.java +@@ -21,24 +21,47 @@ + public SpawnUtil() {} + + public static Optional trySpawnMob(EntityType entityType, EntitySpawnReason reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements, boolean requireEmptySpace) { +- BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); ++ // CraftBukkit start ++ return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, requireEmptySpace, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper - pre creature spawn event ++ } + +- for (int l = 0; l < tries; ++l) { +- int i1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange); +- int j1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange); ++ public static Optional trySpawnMob(EntityType entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) { // Paper - pre creature spawn event ++ // CraftBukkit end ++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); + +- blockposition_mutableblockposition.setWithOffset(pos, i1, verticalRange, j1); +- if (world.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(world, verticalRange, blockposition_mutableblockposition, requirements) && (!requireEmptySpace || world.noCollision(entityType.getSpawnAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D)))) { +- T t0 = (Mob) entityType.create(world, (Consumer) null, blockposition_mutableblockposition, reason, false, false); ++ for (int l = 0; l < i; ++l) { ++ int i1 = Mth.randomBetweenInclusive(worldserver.random, -j, j); ++ int j1 = Mth.randomBetweenInclusive(worldserver.random, -j, j); + ++ blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1); ++ if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a) && (!flag || worldserver.noCollision(entitytypes.getSpawnAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D)))) { ++ // Paper start - PreCreatureSpawnEvent ++ final com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes), ++ reason ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ if (onAbort != null) { ++ onAbort.run(); ++ } ++ return Optional.empty(); ++ } ++ break; ++ } ++ // Paper end - PreCreatureSpawnEvent ++ T t0 = entitytypes.create(worldserver, (Consumer) null, blockposition_mutableblockposition, entityspawnreason, false, false); // CraftBukkit - decompile error ++ + if (t0 != null) { +- if (t0.checkSpawnRules(world, reason) && t0.checkSpawnObstruction(world)) { +- world.addFreshEntityWithPassengers(t0); ++ if (t0.checkSpawnRules(worldserver, entityspawnreason) && t0.checkSpawnObstruction(worldserver)) { ++ worldserver.addFreshEntityWithPassengers(t0, reason); // CraftBukkit ++ if (t0.isRemoved()) return Optional.empty(); // CraftBukkit + t0.playAmbientSound(); + return Optional.of(t0); + } + +- t0.discard(); ++ t0.discard(null); // CraftBukkit - add Bukkit remove cause + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch b/paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch new file mode 100644 index 0000000000..d0f9b93e86 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/util/StringUtil.java ++++ b/net/minecraft/util/StringUtil.java +@@ -67,6 +67,25 @@ + return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty(); + } + ++ // Paper start - Username validation ++ public static boolean isReasonablePlayerName(final String name) { ++ if (name.isEmpty() || name.length() > 16) { ++ return false; ++ } ++ ++ for (int i = 0, len = name.length(); i < len; ++i) { ++ final char c = name.charAt(i); ++ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ } ++ // Paper end - Username validation ++ + public static String filterText(String string) { + return filterText(string, false); + } diff --git a/paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch b/paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch new file mode 100644 index 0000000000..b829a1224e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/util/TickThrottler.java ++++ b/net/minecraft/util/TickThrottler.java +@@ -1,10 +1,14 @@ + package net.minecraft.util; + ++// CraftBukkit start ++import java.util.concurrent.atomic.AtomicInteger; ++// CraftBukkit end ++ + public class TickThrottler { + + private final int incrementStep; + private final int threshold; +- private int count; ++ private final AtomicInteger count = new AtomicInteger(); // CraftBukkit - multithreaded field + + public TickThrottler(int increment, int threshold) { + this.incrementStep = increment; +@@ -12,17 +16,32 @@ + } + + public void increment() { +- this.count += this.incrementStep; ++ this.count.addAndGet(this.incrementStep); // CraftBukkit - use thread-safe field access instead + } + + public void tick() { ++ // CraftBukkit start ++ for (int val; (val = this.count.get()) > 0 && !this.count.compareAndSet(val, val - 1); ) ; ++ /* Use thread-safe field access instead + if (this.count > 0) { + --this.count; + } ++ */ ++ // CraftBukkit end + + } + + public boolean isUnderThreshold() { +- return this.count < this.threshold; ++ // CraftBukkit start - use thread-safe field access instead ++ return this.count.get() < this.threshold; + } ++ ++ public boolean isIncrementAndUnderThreshold() { ++ return this.isIncrementAndUnderThreshold(this.incrementStep, this.threshold); ++ } ++ ++ public boolean isIncrementAndUnderThreshold(int incrementStep, int threshold) { ++ return this.count.addAndGet(incrementStep) < threshold; ++ // CraftBukkit end ++ } + } diff --git a/paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch b/paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch new file mode 100644 index 0000000000..a9e7cdc67b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/util/ZeroBitStorage.java ++++ b/net/minecraft/util/ZeroBitStorage.java +@@ -13,21 +13,21 @@ + } + + @Override +- public int getAndSet(int index, int value) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); +- Validate.inclusiveBetween(0L, 0L, (long)value); ++ public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, 0L, (long)value); // Paper - Perf: Optimize SimpleBitStorage + return 0; + } + + @Override +- public void set(int index, int value) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); +- Validate.inclusiveBetween(0L, 0L, (long)value); ++ public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, 0L, (long)value); // Paper - Perf: Optimize SimpleBitStorage + } + + @Override +- public int get(int index) { +- Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); ++ public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage ++ //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage + return 0; + } + diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch new file mode 100644 index 0000000000..86e8fe50b7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch @@ -0,0 +1,82 @@ +--- a/net/minecraft/util/datafix/DataFixers.java ++++ b/net/minecraft/util/datafix/DataFixers.java +@@ -302,6 +302,18 @@ + builder.addFixer(new EntityItemFrameDirectionFix(schema44, false)); + Schema schema45 = builder.addSchema(1458, DataFixers.SAME_NAMESPACED); + ++ // CraftBukkit start ++ builder.addFixer(new com.mojang.datafixers.DataFix(schema45, false) { ++ @Override ++ protected com.mojang.datafixers.TypeRewriteRule makeRule() { ++ return this.fixTypeEverywhereTyped("Player CustomName", this.getInputSchema().getType(References.PLAYER), (typed) -> { ++ return typed.update(DSL.remainderFinder(), (dynamic) -> { ++ return EntityCustomNameToComponentFix.fixTagCustomName(dynamic); ++ }); ++ }); ++ } ++ }); ++ // CraftBukkit end + builder.addFixer(new EntityCustomNameToComponentFix(schema45, false)); + builder.addFixer(new ItemCustomNameToComponentFix(schema45, false)); + builder.addFixer(new BlockEntityCustomNameToComponentFix(schema45, false)); +@@ -560,7 +572,8 @@ + builder.addFixer(new AddNewChoices(schema110, "Added Zoglin", References.ENTITY)); + Schema schema111 = builder.addSchema(2523, DataFixers.SAME_NAMESPACED); + +- builder.addFixer(new AttributesRenameLegacy(schema111, "Attribute renames", DataFixers.createRenamerNoNamespace(ImmutableMap.builder().put("generic.maxHealth", "minecraft:generic.max_health").put("Max Health", "minecraft:generic.max_health").put("zombie.spawnReinforcements", "minecraft:zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "minecraft:zombie.spawn_reinforcements").put("horse.jumpStrength", "minecraft:horse.jump_strength").put("Jump Strength", "minecraft:horse.jump_strength").put("generic.followRange", "minecraft:generic.follow_range").put("Follow Range", "minecraft:generic.follow_range").put("generic.knockbackResistance", "minecraft:generic.knockback_resistance").put("Knockback Resistance", "minecraft:generic.knockback_resistance").put("generic.movementSpeed", "minecraft:generic.movement_speed").put("Movement Speed", "minecraft:generic.movement_speed").put("generic.flyingSpeed", "minecraft:generic.flying_speed").put("Flying Speed", "minecraft:generic.flying_speed").put("generic.attackDamage", "minecraft:generic.attack_damage").put("generic.attackKnockback", "minecraft:generic.attack_knockback").put("generic.attackSpeed", "minecraft:generic.attack_speed").put("generic.armorToughness", "minecraft:generic.armor_toughness").build()))); ++ // CraftBukkit - decompile error ++ builder.addFixer(new AttributesRenameLegacy(schema111, "Attribute renames", DataFixers.createRenamerNoNamespace(ImmutableMap.builder().put("generic.maxHealth", "minecraft:generic.max_health").put("Max Health", "minecraft:generic.max_health").put("zombie.spawnReinforcements", "minecraft:zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "minecraft:zombie.spawn_reinforcements").put("horse.jumpStrength", "minecraft:horse.jump_strength").put("Jump Strength", "minecraft:horse.jump_strength").put("generic.followRange", "minecraft:generic.follow_range").put("Follow Range", "minecraft:generic.follow_range").put("generic.knockbackResistance", "minecraft:generic.knockback_resistance").put("Knockback Resistance", "minecraft:generic.knockback_resistance").put("generic.movementSpeed", "minecraft:generic.movement_speed").put("Movement Speed", "minecraft:generic.movement_speed").put("generic.flyingSpeed", "minecraft:generic.flying_speed").put("Flying Speed", "minecraft:generic.flying_speed").put("generic.attackDamage", "minecraft:generic.attack_damage").put("generic.attackKnockback", "minecraft:generic.attack_knockback").put("generic.attackSpeed", "minecraft:generic.attack_speed").put("generic.armorToughness", "minecraft:generic.armor_toughness").build()))); + Schema schema112 = builder.addSchema(2527, DataFixers.SAME_NAMESPACED); + + builder.addFixer(new BitStorageAlignFix(schema112)); +@@ -623,12 +636,14 @@ + builder.addFixer(new AddNewChoices(schema130, "Added Glow Squid", References.ENTITY)); + builder.addFixer(new AddNewChoices(schema130, "Added Glow Item Frame", References.ENTITY)); + Schema schema131 = builder.addSchema(2690, DataFixers.SAME_NAMESPACED); +- ImmutableMap immutablemap = ImmutableMap.builder().put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block").put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block").put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block").put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper").put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper").put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper").put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs").put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs").put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs").put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab").put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab").put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab").put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper").put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper").put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper").put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper").put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs").put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs").put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab").put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab").build(); ++ // CraftBukkit - decompile error ++ ImmutableMap immutablemap = ImmutableMap.builder().put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block").put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block").put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block").put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper").put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper").put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper").put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs").put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs").put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs").put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab").put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab").put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab").put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper").put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper").put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper").put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper").put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs").put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs").put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab").put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab").build(); + + builder.addFixer(ItemRenameFix.create(schema131, "Renamed copper block items to new oxidized terms", DataFixers.createRenamer(immutablemap))); + builder.addFixer(BlockRenameFix.create(schema131, "Renamed copper blocks to new oxidized terms", DataFixers.createRenamer(immutablemap))); + Schema schema132 = builder.addSchema(2691, DataFixers.SAME_NAMESPACED); +- ImmutableMap immutablemap1 = ImmutableMap.builder().put("minecraft:waxed_copper", "minecraft:waxed_copper_block").put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper").put("minecraft:weathered_copper_block", "minecraft:weathered_copper").put("minecraft:exposed_copper_block", "minecraft:exposed_copper").build(); ++ // CraftBukkit - decompile error ++ ImmutableMap immutablemap1 = ImmutableMap.builder().put("minecraft:waxed_copper", "minecraft:waxed_copper_block").put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper").put("minecraft:weathered_copper_block", "minecraft:weathered_copper").put("minecraft:exposed_copper_block", "minecraft:exposed_copper").build(); + + builder.addFixer(ItemRenameFix.create(schema132, "Rename copper item suffixes", DataFixers.createRenamer(immutablemap1))); + builder.addFixer(BlockRenameFix.create(schema132, "Rename copper blocks suffixes", DataFixers.createRenamer(immutablemap1))); +@@ -636,7 +651,8 @@ + + builder.addFixer(new AddFlagIfNotPresentFix(schema133, References.WORLD_GEN_SETTINGS, "has_increased_height_already", false)); + Schema schema134 = builder.addSchema(2696, DataFixers.SAME_NAMESPACED); +- ImmutableMap immutablemap2 = ImmutableMap.builder().put("minecraft:grimstone", "minecraft:deepslate").put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab").put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs").put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall").put("minecraft:polished_grimstone", "minecraft:polished_deepslate").put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab").put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs").put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall").put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles").put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab").put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs").put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall").put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks").put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab").put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs").put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall").put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate").build(); ++ // CraftBukkit - decompile error ++ ImmutableMap immutablemap2 = ImmutableMap.builder().put("minecraft:grimstone", "minecraft:deepslate").put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab").put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs").put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall").put("minecraft:polished_grimstone", "minecraft:polished_deepslate").put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab").put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs").put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall").put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles").put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab").put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs").put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall").put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks").put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab").put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs").put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall").put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate").build(); + + builder.addFixer(ItemRenameFix.create(schema134, "Renamed grimstone block items to deepslate", DataFixers.createRenamer(immutablemap2))); + builder.addFixer(BlockRenameFix.create(schema134, "Renamed grimstone blocks to deepslate", DataFixers.createRenamer(immutablemap2))); +@@ -723,10 +739,11 @@ + builder.addFixer(new AddNewChoices(schema159, "Added Allay", References.ENTITY)); + Schema schema160 = builder.addSchema(3084, DataFixers.SAME_NAMESPACED); + +- builder.addFixer(new NamespacedTypeRenameFix(schema160, "game_event_renames_3084", References.GAME_EVENT_NAME, DataFixers.createRenamer(ImmutableMap.builder().put("minecraft:block_press", "minecraft:block_activate").put("minecraft:block_switch", "minecraft:block_activate").put("minecraft:block_unpress", "minecraft:block_deactivate").put("minecraft:block_unswitch", "minecraft:block_deactivate").put("minecraft:drinking_finish", "minecraft:drink").put("minecraft:elytra_free_fall", "minecraft:elytra_glide").put("minecraft:entity_damaged", "minecraft:entity_damage").put("minecraft:entity_dying", "minecraft:entity_die").put("minecraft:entity_killed", "minecraft:entity_die").put("minecraft:mob_interact", "minecraft:entity_interact").put("minecraft:ravager_roar", "minecraft:entity_roar").put("minecraft:ring_bell", "minecraft:block_change").put("minecraft:shulker_close", "minecraft:container_close").put("minecraft:shulker_open", "minecraft:container_open").put("minecraft:wolf_shaking", "minecraft:entity_shake").build()))); ++ // CraftBukkit - decompile error ++ builder.addFixer(new NamespacedTypeRenameFix(schema160, "game_event_renames_3084", References.GAME_EVENT_NAME, DataFixers.createRenamer(ImmutableMap.builder().put("minecraft:block_press", "minecraft:block_activate").put("minecraft:block_switch", "minecraft:block_activate").put("minecraft:block_unpress", "minecraft:block_deactivate").put("minecraft:block_unswitch", "minecraft:block_deactivate").put("minecraft:drinking_finish", "minecraft:drink").put("minecraft:elytra_free_fall", "minecraft:elytra_glide").put("minecraft:entity_damaged", "minecraft:entity_damage").put("minecraft:entity_dying", "minecraft:entity_die").put("minecraft:entity_killed", "minecraft:entity_die").put("minecraft:mob_interact", "minecraft:entity_interact").put("minecraft:ravager_roar", "minecraft:entity_roar").put("minecraft:ring_bell", "minecraft:block_change").put("minecraft:shulker_close", "minecraft:container_close").put("minecraft:shulker_open", "minecraft:container_open").put("minecraft:wolf_shaking", "minecraft:entity_shake").build()))); + Schema schema161 = builder.addSchema(3086, DataFixers.SAME_NAMESPACED); + TypeReference typereference = References.ENTITY; +- Int2ObjectOpenHashMap int2objectopenhashmap = (Int2ObjectOpenHashMap) Util.make(new Int2ObjectOpenHashMap(), (int2objectopenhashmap1) -> { ++ Int2ObjectOpenHashMap int2objectopenhashmap = (Int2ObjectOpenHashMap) Util.make(new Int2ObjectOpenHashMap(), (int2objectopenhashmap1) -> { // CraftBukkit - decompile error + int2objectopenhashmap1.defaultReturnValue("minecraft:tabby"); + int2objectopenhashmap1.put(0, "minecraft:tabby"); + int2objectopenhashmap1.put(1, "minecraft:black"); +@@ -743,7 +760,8 @@ + + Objects.requireNonNull(int2objectopenhashmap); + builder.addFixer(new EntityVariantFix(schema161, "Change cat variant type", typereference, "minecraft:cat", "CatType", int2objectopenhashmap::get)); +- ImmutableMap immutablemap3 = ImmutableMap.builder().put("textures/entity/cat/tabby.png", "minecraft:tabby").put("textures/entity/cat/black.png", "minecraft:black").put("textures/entity/cat/red.png", "minecraft:red").put("textures/entity/cat/siamese.png", "minecraft:siamese").put("textures/entity/cat/british_shorthair.png", "minecraft:british").put("textures/entity/cat/calico.png", "minecraft:calico").put("textures/entity/cat/persian.png", "minecraft:persian").put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll").put("textures/entity/cat/white.png", "minecraft:white").put("textures/entity/cat/jellie.png", "minecraft:jellie").put("textures/entity/cat/all_black.png", "minecraft:all_black").build(); ++ // CraftBukkit - decompile error ++ ImmutableMap immutablemap3 = ImmutableMap.builder().put("textures/entity/cat/tabby.png", "minecraft:tabby").put("textures/entity/cat/black.png", "minecraft:black").put("textures/entity/cat/red.png", "minecraft:red").put("textures/entity/cat/siamese.png", "minecraft:siamese").put("textures/entity/cat/british_shorthair.png", "minecraft:british").put("textures/entity/cat/calico.png", "minecraft:calico").put("textures/entity/cat/persian.png", "minecraft:persian").put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll").put("textures/entity/cat/white.png", "minecraft:white").put("textures/entity/cat/jellie.png", "minecraft:jellie").put("textures/entity/cat/all_black.png", "minecraft:all_black").build(); + + builder.addFixer(new CriteriaRenameFix(schema161, "Migrate cat variant advancement", "minecraft:husbandry/complete_catalogue", (s) -> { + return (String) immutablemap3.getOrDefault(s, s); diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch new file mode 100644 index 0000000000..139090af75 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java ++++ b/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java +@@ -32,7 +32,7 @@ + Typed typed1 = typed.getOrCreateTyped(opticfinder1); + Dynamic dynamic1 = (Dynamic) typed1.get(DSL.remainderFinder()); + +- dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0))); ++ if (!dynamic1.getElement("map").result().isPresent()) dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0))); // CraftBukkit + return typed.set(opticfinder1, typed1.set(DSL.remainderFinder(), dynamic1)); + } else { + return typed; diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch new file mode 100644 index 0000000000..a56a92ae47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java ++++ b/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java +@@ -376,7 +376,7 @@ + Typed typed2 = typed.getOrCreateTyped(opticfinder1); + Dynamic dynamic1 = (Dynamic) typed2.get(DSL.remainderFinder()); + +- dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i)); ++ if (i != 0) dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i)); // CraftBukkit + typed1 = typed1.set(opticfinder1, typed2.set(DSL.remainderFinder(), dynamic1)); + } + diff --git a/paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch b/paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch new file mode 100644 index 0000000000..9692da77b5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/net/minecraft/util/thread/BlockableEventLoop.java +@@ -82,6 +82,13 @@ + runnable.run(); + } + } ++ // Paper start ++ public void scheduleOnMain(Runnable runnable) { ++ // postToMainThread does not work the same as older versions of mc ++ // This method is actually used to create a TickTask, which can then be posted onto main ++ this.schedule(this.wrapRunnable(runnable)); ++ } ++ // Paper end + + @Override + public void schedule(R runnable) { diff --git a/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch new file mode 100644 index 0000000000..de3432baee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/util/worldupdate/WorldUpgrader.java ++++ b/net/minecraft/util/worldupdate/WorldUpgrader.java +@@ -80,7 +80,7 @@ + + public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, RegistryAccess dynamicRegistryManager, boolean eraseCache, boolean recreateRegionFiles) { + this.dimensions = dynamicRegistryManager.lookupOrThrow(Registries.LEVEL_STEM); +- this.levels = (Set) this.dimensions.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); ++ this.levels = (Set) java.util.stream.Stream.of(session.dimensionType).map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); // CraftBukkit + this.eraseCache = eraseCache; + this.dataFixer = dataFixer; + this.levelStorage = session; +@@ -197,9 +197,9 @@ + if (nbttagcompound != null) { + int i = ChunkStorage.getVersion(nbttagcompound); + ChunkGenerator chunkgenerator = ((LevelStem) WorldUpgrader.this.dimensions.getValueOrThrow(Registries.levelToLevelStem(worldKey))).generator(); +- CompoundTag nbttagcompound1 = storage.upgradeChunkTag(worldKey, () -> { ++ CompoundTag nbttagcompound1 = storage.upgradeChunkTag(Registries.levelToLevelStem(worldKey), () -> { // CraftBukkit + return WorldUpgrader.this.overworldDataStorage; +- }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer()); ++ }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer(), chunkPos, null); // CraftBukkit + ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); + + if (!chunkcoordintpair1.equals(chunkPos)) { +@@ -321,7 +321,7 @@ + WorldUpgrader.DimensionToUpgrade worldupgrader_c = (WorldUpgrader.DimensionToUpgrade) iterator.next(); + ResourceKey resourcekey = worldupgrader_c.dimensionKey; + ListIterator listiterator = worldupgrader_c.files; +- T t0 = (AutoCloseable) worldupgrader_c.storage; ++ T t0 = (T) worldupgrader_c.storage; // CraftBukkit - decompile error + + if (listiterator.hasNext()) { + WorldUpgrader.FileToUpgrade worldupgrader_e = (WorldUpgrader.FileToUpgrade) listiterator.next(); diff --git a/paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch b/paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch new file mode 100644 index 0000000000..3210ddc320 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch @@ -0,0 +1,86 @@ +--- a/net/minecraft/world/BossEvent.java ++++ b/net/minecraft/world/BossEvent.java +@@ -13,6 +13,7 @@ + protected boolean darkenScreen; + protected boolean playBossMusic; + protected boolean createWorldFog; ++ public net.kyori.adventure.bossbar.BossBar adventure; // Paper + + public BossEvent(UUID uuid, Component name, BossEvent.BossBarColor color, BossEvent.BossBarOverlay style) { + this.id = uuid; +@@ -27,61 +28,75 @@ + } + + public Component getName() { ++ if (this.adventure != null) return io.papermc.paper.adventure.PaperAdventure.asVanilla(this.adventure.name()); // Paper + return this.name; + } + + public void setName(Component name) { ++ if (this.adventure != null) this.adventure.name(io.papermc.paper.adventure.PaperAdventure.asAdventure(name)); // Paper + this.name = name; + } + + public float getProgress() { ++ if (this.adventure != null) return this.adventure.progress(); // Paper + return this.progress; + } + + public void setProgress(float percent) { ++ if (this.adventure != null) this.adventure.progress(percent); // Paper + this.progress = percent; + } + + public BossEvent.BossBarColor getColor() { ++ if (this.adventure != null) return io.papermc.paper.adventure.PaperAdventure.asVanilla(this.adventure.color()); // Paper + return this.color; + } + + public void setColor(BossEvent.BossBarColor color) { ++ if (this.adventure != null) this.adventure.color(io.papermc.paper.adventure.PaperAdventure.asAdventure(color)); // Paper + this.color = color; + } + + public BossEvent.BossBarOverlay getOverlay() { ++ if (this.adventure != null) return io.papermc.paper.adventure.PaperAdventure.asVanilla(this.adventure.overlay()); // Paper + return this.overlay; + } + + public void setOverlay(BossEvent.BossBarOverlay style) { ++ if (this.adventure != null) this.adventure.overlay(io.papermc.paper.adventure.PaperAdventure.asAdventure(style)); // Paper + this.overlay = style; + } + + public boolean shouldDarkenScreen() { ++ if (this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.DARKEN_SCREEN); // Paper + return this.darkenScreen; + } + + public BossEvent setDarkenScreen(boolean darkenSky) { ++ if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.DARKEN_SCREEN, darkenSky); // Paper + this.darkenScreen = darkenSky; + return this; + } + + public boolean shouldPlayBossMusic() { ++ if (this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC); // Paper + return this.playBossMusic; + } + + public BossEvent setPlayBossMusic(boolean dragonMusic) { ++ if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC, dragonMusic); // Paper + this.playBossMusic = dragonMusic; + return this; + } + + public BossEvent setCreateWorldFog(boolean thickenFog) { ++ if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG, thickenFog); // Paper + this.createWorldFog = thickenFog; + return this; + } + + public boolean shouldCreateWorldFog() { ++ if (this.adventure != null) return this.adventure.hasFlag(net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG); // Paper + return this.createWorldFog; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch new file mode 100644 index 0000000000..1a360b99ad --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/CompoundContainer.java ++++ b/net/minecraft/world/CompoundContainer.java +@@ -3,11 +3,62 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.List; ++import org.bukkit.Location; ++ ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class CompoundContainer implements Container { + + public final Container container1; + public final Container container2; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ ++ public List getContents() { ++ List result = new ArrayList(this.getContainerSize()); ++ for (int i = 0; i < this.getContainerSize(); i++) { ++ result.add(this.getItem(i)); ++ } ++ return result; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.container1.onOpen(who); ++ this.container2.onOpen(who); ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.container1.onClose(who); ++ this.container2.onClose(who); ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.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.container1.setMaxStackSize(size); ++ this.container2.setMaxStackSize(size); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.container1.getLocation(); // TODO: right? ++ } ++ // CraftBukkit end ++ + public CompoundContainer(Container first, Container second) { + this.container1 = first; + this.container2 = second; +@@ -54,7 +105,7 @@ + + @Override + public int getMaxStackSize() { +- return this.container1.getMaxStackSize(); ++ return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // CraftBukkit - check both sides + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/Container.java.patch b/paper-server/patches/sources/net/minecraft/world/Container.java.patch new file mode 100644 index 0000000000..cc552af92d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/Container.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/Container.java ++++ b/net/minecraft/world/Container.java +@@ -6,8 +6,12 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; ++// CraftBukkit start ++import net.minecraft.world.item.crafting.RecipeHolder; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.BlockEntity; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++// CraftBukkit end + + public interface Container extends Clearable { + +@@ -25,9 +29,7 @@ + + void setItem(int slot, ItemStack stack); + +- default int getMaxStackSize() { +- return 99; +- } ++ int getMaxStackSize(); // CraftBukkit + + default int getMaxStackSize(ItemStack stack) { + return Math.min(this.getMaxStackSize(), stack.getMaxStackSize()); +@@ -91,4 +93,22 @@ + + return world == null ? false : (world.getBlockEntity(blockposition) != blockEntity ? false : player.canInteractWithBlock(blockposition, (double) range)); + } ++ ++ // CraftBukkit start ++ java.util.List getContents(); ++ ++ void onOpen(CraftHumanEntity who); ++ ++ void onClose(CraftHumanEntity who); ++ ++ java.util.List getViewers(); ++ ++ org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation ++ ++ void setMaxStackSize(int size); ++ ++ org.bukkit.Location getLocation(); ++ ++ int MAX_STACK = 99; ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch new file mode 100644 index 0000000000..a79a33da44 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch @@ -0,0 +1,94 @@ +--- a/net/minecraft/world/RandomizableContainer.java ++++ b/net/minecraft/world/RandomizableContainer.java +@@ -28,7 +28,7 @@ + + void setLootTable(@Nullable ResourceKey lootTable); + +- default void setLootTable(ResourceKey lootTableId, long lootTableSeed) { ++ default void setLootTable(@Nullable ResourceKey lootTableId, long lootTableSeed) { // Paper - add nullable + this.setLootTable(lootTableId); + this.setLootTableSeed(lootTableSeed); + } +@@ -50,14 +50,15 @@ + + default boolean tryLoadLootTable(CompoundTag nbt) { + if (nbt.contains("LootTable", 8)) { +- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); ++ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation ++ if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API + if (nbt.contains("LootTableSeed", 4)) { + this.setLootTableSeed(nbt.getLong("LootTableSeed")); + } else { + this.setLootTableSeed(0L); + } + +- return true; ++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish + } else { + return false; + } +@@ -69,26 +70,44 @@ + return false; + } else { + nbt.putString("LootTable", resourceKey.location().toString()); ++ if (this.lootableData() != null) this.lootableData().saveNbt(nbt); // Paper - LootTable API + long l = this.getLootTableSeed(); + if (l != 0L) { + nbt.putLong("LootTableSeed", l); + } + +- return true; ++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish + } + } + + default void unpackLootTable(@Nullable Player player) { ++ // Paper start - LootTable API ++ this.unpackLootTable(player, false); ++ } ++ default void unpackLootTable(@Nullable final Player player, final boolean forceClearLootTable) { ++ // Paper end - LootTable API + Level level = this.getLevel(); + BlockPos blockPos = this.getBlockPos(); + ResourceKey resourceKey = this.getLootTable(); +- if (resourceKey != null && level != null && level.getServer() != null) { ++ // Paper start - LootTable API ++ lootReplenish: if (resourceKey != null && level != null && level.getServer() != null) { ++ if (this.lootableData() != null && !this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) { ++ if (forceClearLootTable) { ++ this.setLootTable(null); ++ } ++ break lootReplenish; ++ } ++ // Paper end - LootTable API + LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(resourceKey); + if (player instanceof ServerPlayer) { + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, resourceKey); + } + +- this.setLootTable(null); ++ // Paper start - LootTable API ++ if (forceClearLootTable || this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) { ++ this.setLootTable(null); ++ } ++ // Paper end - LootTable API + LootParams.Builder builder = new LootParams.Builder((ServerLevel)level).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos)); + if (player != null) { + builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player); +@@ -97,4 +116,16 @@ + lootTable.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed()); + } + } ++ ++ // Paper start - LootTable API ++ @Nullable @org.jetbrains.annotations.Contract(pure = true) ++ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ return null; // some containers don't really have a "replenish" ability like decorated pots ++ } ++ ++ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() { ++ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(java.util.Objects.requireNonNull(this.getLevel(), "Cannot manage loot tables on block entities not in world"), this.getBlockPos()); ++ return (com.destroystokyo.paper.loottable.PaperLootableInventory) block.getState(false); ++ } ++ // Paper end - LootTable API + } diff --git a/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch new file mode 100644 index 0000000000..ff3587753c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch @@ -0,0 +1,103 @@ +--- a/net/minecraft/world/SimpleContainer.java ++++ b/net/minecraft/world/SimpleContainer.java +@@ -14,18 +14,98 @@ + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class SimpleContainer implements Container, StackedContentsCompatible { + + private final int size; + public final NonNullList items; + @Nullable + private List listeners; ++ ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int i) { ++ this.maxStack = i; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ // Paper start - Add missing InventoryHolders ++ if (this.bukkitOwner == null && this.bukkitOwnerCreator != null) { ++ this.bukkitOwner = this.bukkitOwnerCreator.get(); ++ } ++ // Paper end - Add missing InventoryHolders ++ return this.bukkitOwner; ++ } + ++ @Override ++ public Location getLocation() { ++ // Paper start - Fix inventories returning null Locations ++ // When the block inventory does not have a tile state that implements getLocation, e. g. composters ++ if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) { ++ return blockInventoryHolder.getBlock().getLocation(); ++ } ++ // When the bukkit owner is a bukkit entity, but does not implement Container itself, e. g. horses ++ if (this.bukkitOwner instanceof org.bukkit.entity.Entity entity) { ++ return entity.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations ++ return null; ++ } ++ ++ public SimpleContainer(SimpleContainer original) { ++ this(original.size); ++ for (int slot = 0; slot < original.size; slot++) { ++ this.items.set(slot, original.items.get(slot).copy()); ++ } ++ } ++ + public SimpleContainer(int size) { +- this.size = size; +- this.items = NonNullList.withSize(size, ItemStack.EMPTY); ++ this(size, null); ++ } ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier bukkitOwnerCreator; ++ public SimpleContainer(java.util.function.Supplier bukkitOwnerCreator, int size) { ++ this(size); ++ this.bukkitOwnerCreator = bukkitOwnerCreator; + } ++ // Paper end - Add missing InventoryHolders + ++ public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) { ++ this.bukkitOwner = owner; ++ // CraftBukkit end ++ this.size = i; ++ this.items = NonNullList.withSize(i, ItemStack.EMPTY); ++ } ++ + public SimpleContainer(ItemStack... items) { + this.size = items.length; + this.items = NonNullList.of(ItemStack.EMPTY, items); diff --git a/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch new file mode 100644 index 0000000000..65bbca9531 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch @@ -0,0 +1,128 @@ +--- a/net/minecraft/world/damagesource/DamageSource.java ++++ b/net/minecraft/world/damagesource/DamageSource.java +@@ -21,7 +21,106 @@ + private final Entity directEntity; + @Nullable + private final Vec3 damageSourcePosition; ++ // CraftBukkit start ++ @Nullable ++ private org.bukkit.block.Block directBlock; // The block that caused the damage. damageSourcePosition is not used for all block damages ++ @Nullable ++ private org.bukkit.block.BlockState directBlockState; // The block state of the block relevant to this damage source ++ private boolean sweep = false; ++ private boolean melting = false; ++ private boolean poison = false; ++ @Nullable ++ private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API + ++ public DamageSource sweep() { ++ this.sweep = true; ++ return this; ++ } ++ ++ public boolean isSweep() { ++ return this.sweep; ++ } ++ ++ public DamageSource melting() { ++ this.melting = true; ++ return this; ++ } ++ ++ public boolean isMelting() { ++ return this.melting; ++ } ++ ++ public DamageSource poison() { ++ this.poison = true; ++ return this; ++ } ++ ++ public boolean isPoison() { ++ return this.poison; ++ } ++ ++ // Paper start - fix DamageSource API ++ @Nullable ++ public Entity getCustomEventDamager() { ++ return (this.customEventDamager != null) ? this.customEventDamager : this.directEntity; ++ } ++ ++ public DamageSource customEventDamager(Entity entity) { ++ if (this.directEntity != null) { ++ throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper)"); ++ } ++ DamageSource damageSource = this.cloneInstance(); ++ damageSource.customEventDamager = entity; ++ // Paper end - fix DamageSource API ++ return damageSource; ++ } ++ ++ public org.bukkit.block.Block getDirectBlock() { ++ return this.directBlock; ++ } ++ ++ public DamageSource directBlock(net.minecraft.world.level.Level world, net.minecraft.core.BlockPos blockPosition) { ++ if (blockPosition == null || world == null) { ++ return this; ++ } ++ return this.directBlock(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockPosition)); ++ } ++ ++ public DamageSource directBlock(org.bukkit.block.Block block) { ++ if (block == null) { ++ return this; ++ } ++ // Cloning the instance lets us return unique instances of DamageSource without affecting constants defined in DamageSources ++ DamageSource damageSource = this.cloneInstance(); ++ damageSource.directBlock = block; ++ return damageSource; ++ } ++ ++ public org.bukkit.block.BlockState getDirectBlockState() { ++ return this.directBlockState; ++ } ++ ++ public DamageSource directBlockState(org.bukkit.block.BlockState blockState) { ++ if (blockState == null) { ++ return this; ++ } ++ // Cloning the instance lets us return unique instances of DamageSource without affecting constants defined in DamageSources ++ DamageSource damageSource = this.cloneInstance(); ++ damageSource.directBlockState = blockState; ++ return damageSource; ++ } ++ ++ private DamageSource cloneInstance() { ++ DamageSource damageSource = new DamageSource(this.type, this.directEntity, this.causingEntity, this.damageSourcePosition); ++ damageSource.directBlock = this.getDirectBlock(); ++ damageSource.directBlockState = this.getDirectBlockState(); ++ damageSource.sweep = this.isSweep(); ++ damageSource.poison = this.isPoison(); ++ damageSource.melting = this.isMelting(); ++ return damageSource; ++ } ++ // CraftBukkit end ++ + public String toString() { + return "DamageSource (" + this.type().msgId() + ")"; + } +@@ -163,4 +262,18 @@ + public Holder typeHolder() { + return this.type; + } ++ ++ // Paper start - add critical damage API ++ private boolean critical; ++ public boolean isCritical() { ++ return this.critical; ++ } ++ public DamageSource critical() { ++ return this.critical(true); ++ } ++ public DamageSource critical(boolean critical) { ++ this.critical = critical; ++ return this; ++ } ++ // Paper end - add critical damage API + } diff --git a/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch new file mode 100644 index 0000000000..ef590e6374 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch @@ -0,0 +1,62 @@ +--- a/net/minecraft/world/damagesource/DamageSources.java ++++ b/net/minecraft/world/damagesource/DamageSources.java +@@ -43,9 +43,15 @@ + private final DamageSource stalagmite; + private final DamageSource outsideBorder; + private final DamageSource genericKill; ++ // CraftBukkit start ++ private final DamageSource melting; ++ private final DamageSource poison; + + public DamageSources(RegistryAccess registryManager) { + this.damageTypes = registryManager.lookupOrThrow(Registries.DAMAGE_TYPE); ++ this.melting = this.source(DamageTypes.ON_FIRE).melting(); ++ this.poison = this.source(DamageTypes.MAGIC).poison(); ++ // CraftBukkit end + this.inFire = this.source(DamageTypes.IN_FIRE); + this.campfire = this.source(DamageTypes.CAMPFIRE); + this.lightningBolt = this.source(DamageTypes.LIGHTNING_BOLT); +@@ -83,7 +89,17 @@ + + private DamageSource source(ResourceKey key, @Nullable Entity source, @Nullable Entity attacker) { + return new DamageSource(this.damageTypes.getOrThrow(key), source, attacker); ++ } ++ ++ // CraftBukkit start ++ public DamageSource melting() { ++ return this.melting; ++ } ++ ++ public DamageSource poison() { ++ return this.poison; + } ++ // CraftBukkit end + + public DamageSource inFire() { + return this.inFire; +@@ -254,7 +270,7 @@ + } + + public DamageSource explosion(@Nullable Entity source, @Nullable Entity attacker) { +- return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker); ++ return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker); // Paper - revert to vanilla + } + + public DamageSource sonicBoom(Entity attacker) { +@@ -262,9 +278,15 @@ + } + + public DamageSource badRespawnPointExplosion(Vec3 position) { +- return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), position); ++ // CraftBukkit start ++ return this.badRespawnPointExplosion(position, null); + } + ++ public DamageSource badRespawnPointExplosion(Vec3 vec3d, org.bukkit.block.BlockState blockState) { ++ return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), vec3d).directBlockState(blockState); ++ // CraftBukkit end ++ } ++ + public DamageSource outOfBorder() { + return this.outsideBorder; + } diff --git a/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch new file mode 100644 index 0000000000..d8a62a429d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/effect/HealOrHarmMobEffect.java ++++ b/net/minecraft/world/effect/HealOrHarmMobEffect.java +@@ -17,7 +17,7 @@ + @Override + public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + if (this.isHarm == entity.isInvertedHealAndHarm()) { +- entity.heal((float) Math.max(4 << amplifier, 0)); ++ entity.heal((float) Math.max(4 << amplifier, 0), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit + } else { + entity.hurtServer(world, entity.damageSources().magic(), (float) (6 << amplifier)); + } +@@ -31,7 +31,7 @@ + + if (this.isHarm == target.isInvertedHealAndHarm()) { + j = (int) (proximity * (double) (4 << amplifier) + 0.5D); +- target.heal((float) j); ++ target.heal((float) j, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit + } else { + j = (int) (proximity * (double) (6 << amplifier) + 0.5D); + if (effectEntity == null) { diff --git a/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch new file mode 100644 index 0000000000..9757b68e1a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/effect/HungerMobEffect.java ++++ b/net/minecraft/world/effect/HungerMobEffect.java +@@ -13,7 +13,7 @@ + @Override + public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + if (entity instanceof Player entityhuman) { +- entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1)); ++ entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent + } + + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch new file mode 100644 index 0000000000..98497613dc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/effect/InfestedMobEffect.java ++++ b/net/minecraft/world/effect/InfestedMobEffect.java +@@ -48,7 +48,11 @@ + + entitysilverfish.moveTo(x, y, z, world.getRandom().nextFloat() * 360.0F, 0.0F); + entitysilverfish.setDeltaMovement(new Vec3(vector3f)); +- world.addFreshEntity(entitysilverfish); ++ // CraftBukkit start ++ if (!world.addFreshEntity(entitysilverfish, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) { ++ return; ++ } ++ // CraftBukkit end + entitysilverfish.playSound(SoundEvents.SILVERFISH_HURT); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch new file mode 100644 index 0000000000..6c259deb9b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/effect/MobEffectUtil.java ++++ b/net/minecraft/world/effect/MobEffectUtil.java +@@ -50,13 +50,32 @@ + } + + public static List addEffectToPlayersAround(ServerLevel world, @Nullable Entity entity, Vec3 origin, double range, MobEffectInstance statusEffectInstance, int duration) { +- Holder holder = statusEffectInstance.getEffect(); +- List list = world.getPlayers((entityplayer) -> { +- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && origin.closerThan(entityplayer.position(), range) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < statusEffectInstance.getAmplifier() || entityplayer.getEffect(holder).endsWithin(duration - 1)); ++ // CraftBukkit start ++ return MobEffectUtil.addEffectToPlayersAround(world, entity, origin, range, statusEffectInstance, duration, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN); ++ } ++ ++ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { ++ // Paper start - Add ElderGuardianAppearanceEvent ++ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null); ++ } ++ ++ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate playerPredicate) { ++ // Paper end - Add ElderGuardianAppearanceEvent ++ // CraftBukkit end ++ Holder holder = mobeffect.getEffect(); ++ List list = worldserver.getPlayers((entityplayer) -> { ++ // Paper start - Add ElderGuardianAppearanceEvent ++ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1)); ++ if (condition) { ++ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true ++ } else { ++ return false; ++ } ++ // Paper ned - Add ElderGuardianAppearanceEvent + }); + + list.forEach((entityplayer) -> { +- entityplayer.addEffect(new MobEffectInstance(statusEffectInstance), entity); ++ entityplayer.addEffect(new MobEffectInstance(mobeffect), entity, cause); // CraftBukkit + }); + return list; + } diff --git a/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch new file mode 100644 index 0000000000..4a2bef9952 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/effect/OozingMobEffect.java ++++ b/net/minecraft/world/effect/OozingMobEffect.java +@@ -52,7 +52,7 @@ + if (entityslime != null) { + entityslime.setSize(2, true); + entityslime.moveTo(x, y, z, world.getRandom().nextFloat() * 360.0F, 0.0F); +- world.addFreshEntity(entityslime); ++ world.addFreshEntity(entityslime, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch new file mode 100644 index 0000000000..367358c2d8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/effect/PoisonMobEffect.java ++++ b/net/minecraft/world/effect/PoisonMobEffect.java +@@ -14,7 +14,7 @@ + @Override + public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + if (entity.getHealth() > 1.0F) { +- entity.hurtServer(world, entity.damageSources().magic(), 1.0F); ++ entity.hurtServer(world, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON + } + + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch new file mode 100644 index 0000000000..9a24fc0330 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/effect/RegenerationMobEffect.java ++++ b/net/minecraft/world/effect/RegenerationMobEffect.java +@@ -12,7 +12,7 @@ + @Override + public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + if (entity.getHealth() < entity.getMaxHealth()) { +- entity.heal(1.0F); ++ entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit + } + + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch new file mode 100644 index 0000000000..5d38f090c8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/effect/SaturationMobEffect.java ++++ b/net/minecraft/world/effect/SaturationMobEffect.java +@@ -3,6 +3,10 @@ + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.player.Player; ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + class SaturationMobEffect extends InstantenousMobEffect { + +@@ -13,7 +17,15 @@ + @Override + public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + if (entity instanceof Player entityhuman) { +- entityhuman.getFoodData().eat(amplifier + 1, 1.0F); ++ // CraftBukkit start ++ int oldFoodLevel = entityhuman.getFoodData().foodLevel; ++ org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); ++ if (!event.isCancelled()) { ++ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); ++ } ++ ++ ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate(); ++ // CraftBukkit end + } + + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch new file mode 100644 index 0000000000..8beb585d3f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/effect/WeavingMobEffect.java ++++ b/net/minecraft/world/effect/WeavingMobEffect.java +@@ -25,11 +25,11 @@ + @Override + public void onMobRemoved(ServerLevel world, LivingEntity entity, int amplifier, Entity.RemovalReason reason) { + if (reason == Entity.RemovalReason.KILLED && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { +- this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition()); ++ this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition(), entity); // Paper - Fire EntityChangeBlockEvent in more places + } + } + +- private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos) { ++ private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos, LivingEntity entity) { // Paper - Fire EntityChangeBlockEvent in more places + Set set = Sets.newHashSet(); + int i = this.maxCobwebs.applyAsInt(random); + +@@ -46,6 +46,7 @@ + } + + for (BlockPos blockPos3 : set) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, Blocks.COBWEB.defaultBlockState())) continue; // Paper - Fire EntityChangeBlockEvent in more places + world.setBlock(blockPos3, Blocks.COBWEB.defaultBlockState(), 3); + world.levelEvent(3018, blockPos3, 0); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch new file mode 100644 index 0000000000..5748ed58a8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/entity/AgeableMob.java ++++ b/net/minecraft/world/entity/AgeableMob.java +@@ -21,12 +21,38 @@ + protected int age; + protected int forcedAge; + protected int forcedAgeTimer; ++ public boolean ageLocked; // CraftBukkit + + protected AgeableMob(EntityType type, Level world) { + super(type, world); + } + ++ // Spigot start + @Override ++ public void inactiveTick() ++ { ++ super.inactiveTick(); ++ if ( this.level().isClientSide || this.ageLocked ) ++ { // CraftBukkit ++ this.refreshDimensions(); ++ } else ++ { ++ int i = this.getAge(); ++ ++ if ( i < 0 ) ++ { ++ ++i; ++ this.setAge( i ); ++ } else if ( i > 0 ) ++ { ++ --i; ++ this.setAge( i ); ++ } ++ } ++ } ++ // Spigot end ++ ++ @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { + if (entityData == null) { + entityData = new AgeableMob.AgeableMobGroupData(true); +@@ -60,6 +86,7 @@ + } + + public void ageUp(int age, boolean overGrow) { ++ if (this.ageLocked) return; // Paper - Honor ageLock + int j = this.getAge(); + int k = j; + +@@ -104,6 +131,7 @@ + super.addAdditionalSaveData(nbt); + nbt.putInt("Age", this.getAge()); + nbt.putInt("ForcedAge", this.forcedAge); ++ nbt.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit + } + + @Override +@@ -111,6 +139,7 @@ + super.readAdditionalSaveData(nbt); + this.setAge(nbt.getInt("Age")); + this.forcedAge = nbt.getInt("ForcedAge"); ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // CraftBukkit + } + + @Override +@@ -125,7 +154,7 @@ + @Override + public void aiStep() { + super.aiStep(); +- if (this.level().isClientSide) { ++ if (this.level().isClientSide || this.ageLocked) { // CraftBukkit + if (this.forcedAgeTimer > 0) { + if (this.forcedAgeTimer % 4 == 0) { + this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), 0.0D, 0.0D, 0.0D); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch new file mode 100644 index 0000000000..835489e22c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch @@ -0,0 +1,160 @@ +--- a/net/minecraft/world/entity/AreaEffectCloud.java ++++ b/net/minecraft/world/entity/AreaEffectCloud.java +@@ -33,6 +33,12 @@ + import net.minecraft.world.level.material.PushReaction; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end ++ + public class AreaEffectCloud extends Entity implements TraceableEntity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -54,7 +60,7 @@ + public float radiusOnUse; + public float radiusPerTick; + @Nullable +- private LivingEntity owner; ++ private net.minecraft.world.entity.LivingEntity owner; + @Nullable + public UUID ownerUUID; + +@@ -145,7 +151,19 @@ + this.duration = duration; + } + ++ // Spigot start - copied from below + @Override ++ public void inactiveTick() { ++ super.inactiveTick(); ++ ++ if (this.tickCount >= this.waitTime + this.duration) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ return; ++ } ++ } ++ // Spigot end ++ ++ @Override + public void tick() { + super.tick(); + Level world = this.level(); +@@ -200,7 +218,7 @@ + + private void serverTick(ServerLevel world) { + if (this.tickCount >= this.waitTime + this.duration) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + boolean flag = this.isWaiting(); + boolean flag1 = this.tickCount < this.waitTime; +@@ -215,7 +233,7 @@ + if (this.radiusPerTick != 0.0F) { + f += this.radiusPerTick; + if (f < 0.5F) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; + } + +@@ -244,16 +262,17 @@ + } + + list.addAll(this.potionContents.customEffects()); +- List list1 = this.level().getEntitiesOfClass(LivingEntity.class, this.getBoundingBox()); ++ List list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, this.getBoundingBox()); + + if (!list1.isEmpty()) { + Iterator iterator1 = list1.iterator(); + ++ List entities = new java.util.ArrayList(); // CraftBukkit + while (iterator1.hasNext()) { +- LivingEntity entityliving = (LivingEntity) iterator1.next(); ++ net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator1.next(); + + if (!this.victims.containsKey(entityliving) && entityliving.isAffectedByPotions()) { +- Stream stream = list.stream(); ++ Stream stream = list.stream(); // CraftBukkit - decompile error + + Objects.requireNonNull(entityliving); + if (!stream.noneMatch(entityliving::canBeAffected)) { +@@ -262,6 +281,19 @@ + double d2 = d0 * d0 + d1 * d1; + + if (d2 <= (double) (f * f)) { ++ // CraftBukkit start ++ entities.add((LivingEntity) entityliving.getBukkitEntity()); ++ } ++ } ++ } ++ } ++ { ++ org.bukkit.event.entity.AreaEffectCloudApplyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callAreaEffectCloudApplyEvent(this, entities); ++ if (!event.isCancelled()) { ++ for (LivingEntity entity : event.getAffectedEntities()) { ++ if (entity instanceof CraftLivingEntity) { ++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) entity).getHandle(); ++ // CraftBukkit end + this.victims.put(entityliving, this.tickCount + this.reapplicationDelay); + Iterator iterator2 = list.iterator(); + +@@ -271,14 +303,14 @@ + if (((MobEffect) mobeffect1.getEffect().value()).isInstantenous()) { + ((MobEffect) mobeffect1.getEffect().value()).applyInstantenousEffect(world, this, this.getOwner(), entityliving, mobeffect1.getAmplifier(), 0.5D); + } else { +- entityliving.addEffect(new MobEffectInstance(mobeffect1), this); ++ entityliving.addEffect(new MobEffectInstance(mobeffect1), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AREA_EFFECT_CLOUD); // CraftBukkit + } + } + + if (this.radiusOnUse != 0.0F) { + f += this.radiusOnUse; + if (f < 0.5F) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; + } + +@@ -288,7 +320,7 @@ + if (this.durationOnUse != 0) { + this.duration += this.durationOnUse; + if (this.duration <= 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; + } + } +@@ -336,14 +368,14 @@ + this.waitTime = waitTime; + } + +- public void setOwner(@Nullable LivingEntity owner) { ++ public void setOwner(@Nullable net.minecraft.world.entity.LivingEntity owner) { + this.owner = owner; + this.ownerUUID = owner == null ? null : owner.getUUID(); + } + + @Nullable + @Override +- public LivingEntity getOwner() { ++ public net.minecraft.world.entity.LivingEntity getOwner() { + if (this.owner != null && !this.owner.isRemoved()) { + return this.owner; + } else { +@@ -353,10 +385,10 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + Entity entity = worldserver.getEntity(this.ownerUUID); +- LivingEntity entityliving; ++ net.minecraft.world.entity.LivingEntity entityliving; + +- if (entity instanceof LivingEntity) { +- LivingEntity entityliving1 = (LivingEntity) entity; ++ if (entity instanceof net.minecraft.world.entity.LivingEntity) { ++ net.minecraft.world.entity.LivingEntity entityliving1 = (net.minecraft.world.entity.LivingEntity) entity; + + entityliving = entityliving1; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch new file mode 100644 index 0000000000..3673cd3667 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/ConversionParams.java ++++ b/net/minecraft/world/entity/ConversionParams.java +@@ -12,4 +12,11 @@ + public interface AfterConversion { + void finalizeConversion(T convertedEntity); + } ++ ++ // Paper start - entity zap event - allow conversion to be cancelled during finalization ++ @FunctionalInterface ++ public interface CancellingAfterConversion { ++ boolean finalizeConversionOrCancel(final T convertedEntity); ++ } ++ // Paper start - entity zap event - allow conversion to be cancelled during finalization + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch new file mode 100644 index 0000000000..ed6daf39bf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/ConversionType.java ++++ b/net/minecraft/world/entity/ConversionType.java +@@ -4,6 +4,7 @@ + import java.util.Objects; + import java.util.Optional; + import java.util.Set; ++import net.minecraft.core.BlockPos; + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.entity.ai.Brain; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; +@@ -11,6 +12,8 @@ + import net.minecraft.world.entity.monster.Zombie; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.scores.Scoreboard; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public enum ConversionType { + +@@ -31,7 +34,7 @@ + while (iterator.hasNext()) { + entity1 = (Entity) iterator.next(); + entity1.stopRiding(); +- entity1.remove(Entity.RemovalReason.DISCARDED); ++ entity1.remove(Entity.RemovalReason.DISCARDED, EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause + } + + entity.startRiding(newEntity); +@@ -64,7 +67,7 @@ + newEntity.hurtTime = oldEntity.hurtTime; + newEntity.yBodyRot = oldEntity.yBodyRot; + newEntity.setOnGround(oldEntity.onGround()); +- Optional optional = oldEntity.getSleepingPos(); ++ Optional optional = oldEntity.getSleepingPos(); // CraftBukkit - decompile error + + Objects.requireNonNull(newEntity); + optional.ifPresent(newEntity::setSleepingPos); +@@ -156,7 +159,7 @@ + newEntity.setNoGravity(oldEntity.isNoGravity()); + newEntity.setPortalCooldown(oldEntity.getPortalCooldown()); + newEntity.setSilent(oldEntity.isSilent()); +- Set set = oldEntity.getTags(); ++ Set set = oldEntity.getTags(); // CraftBukkit - decompile error + + Objects.requireNonNull(newEntity); + set.forEach(newEntity::addTag); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch new file mode 100644 index 0000000000..893b7a911c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/Display.java ++++ b/net/minecraft/world/entity/Display.java +@@ -903,7 +903,7 @@ + b = loadFlag(b, nbt, "default_background", (byte)4); + Optional optional = Display.TextDisplay.Align.CODEC + .decode(NbtOps.INSTANCE, nbt.get("alignment")) +- .resultOrPartial(Util.prefix("Display entity", Display.LOGGER::error)) ++ .result() // Paper - Hide text display error on spawn + .map(Pair::getFirst); + if (optional.isPresent()) { + b = switch ((Display.TextDisplay.Align)optional.get()) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch new file mode 100644 index 0000000000..a274089f56 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -0,0 +1,1994 @@ +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -59,6 +59,8 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; + import net.minecraft.network.protocol.game.VecDeltaCodec; + import net.minecraft.network.syncher.EntityDataAccessor; +@@ -101,8 +103,6 @@ + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Explosion; +-import net.minecraft.world.level.ItemLike; +-import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.FenceGateBlock; +@@ -138,9 +138,153 @@ + import net.minecraft.world.scores.ScoreHolder; + import net.minecraft.world.scores.Team; + import org.slf4j.Logger; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.ItemLike; ++import net.minecraft.world.level.Level; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.Server; ++import org.bukkit.block.BlockFace; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Hanging; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++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.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Pose; ++import org.bukkit.event.entity.EntityAirChangeEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++import org.bukkit.event.entity.EntityDismountEvent; ++import org.bukkit.event.entity.EntityDropItemEvent; ++import org.bukkit.event.entity.EntityMountEvent; ++import org.bukkit.event.entity.EntityPortalEvent; ++import org.bukkit.event.entity.EntityPoseChangeEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.event.entity.EntityUnleashEvent; ++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.plugin.PluginManager; ++// CraftBukkit end + + public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder { + ++ // CraftBukkit start ++ private static final int CURRENT_LEVEL = 2; ++ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation ++ static boolean isLevelAtLeast(CompoundTag tag, int level) { ++ return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; ++ } ++ ++ // Paper start - Share random for entities to make them more random ++ public static RandomSource SHARED_RANDOM = new RandomRandomSource(); ++ private static final class RandomRandomSource extends java.util.Random implements net.minecraft.world.level.levelgen.BitRandomSource { ++ private boolean locked = false; ++ ++ @Override ++ public synchronized void setSeed(long seed) { ++ if (locked) { ++ LOGGER.error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); ++ } else { ++ super.setSeed(seed); ++ locked = true; ++ } ++ } ++ ++ @Override ++ public RandomSource fork() { ++ return new net.minecraft.world.level.levelgen.LegacyRandomSource(this.nextLong()); ++ } ++ ++ @Override ++ public net.minecraft.world.level.levelgen.PositionalRandomFactory forkPositional() { ++ return new net.minecraft.world.level.levelgen.LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong()); ++ } ++ ++ // these below are added to fix reobf issues that I don't wanna deal with right now ++ @Override ++ public int next(int bits) { ++ return super.next(bits); ++ } ++ ++ @Override ++ public int nextInt(int origin, int bound) { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound); ++ } ++ ++ @Override ++ public long nextLong() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextLong(); ++ } ++ ++ @Override ++ public int nextInt() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(bound); ++ } ++ ++ @Override ++ public boolean nextBoolean() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextBoolean(); ++ } ++ ++ @Override ++ public float nextFloat() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextFloat(); ++ } ++ ++ @Override ++ public double nextDouble() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextDouble(); ++ } ++ ++ @Override ++ public double nextGaussian() { ++ return super.nextGaussian(); ++ } ++ } ++ // Paper end - Share random for entities to make them more random ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason ++ ++ private CraftEntity bukkitEntity; ++ ++ public CraftEntity getBukkitEntity() { ++ if (this.bukkitEntity == null) { ++ // Paper start - Folia schedulers ++ synchronized (this) { ++ if (this.bukkitEntity == null) { ++ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ } ++ } ++ // Paper end - Folia schedulers ++ } ++ return this.bukkitEntity; ++ } ++ // Paper start ++ public CraftEntity getBukkitEntityRaw() { ++ return this.bukkitEntity; ++ } ++ // Paper end ++ ++ // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ public int getDefaultMaxAirSupply() { ++ return Entity.TOTAL_AIR_SUPPLY; ++ } ++ // CraftBukkit end ++ + private static final Logger LOGGER = LogUtils.getLogger(); + public static final String ID_TAG = "id"; + public static final String PASSENGERS_TAG = "Passengers"; +@@ -224,7 +368,7 @@ + private static final EntityDataAccessor DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); +- protected static final EntityDataAccessor DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); ++ protected static final EntityDataAccessor DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); + private static final EntityDataAccessor DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT); + private EntityInLevelCallback levelCallback; + private final VecDeltaCodec packetPositionCodec; +@@ -253,15 +397,78 @@ + private final List movementThisTick; + private final Set blocksInside; + private final LongSet visitedBlocks; ++ // CraftBukkit start ++ public boolean forceDrops; ++ public boolean persist = true; ++ public boolean visibleByDefault = true; ++ public boolean valid; ++ public boolean inWorld = false; ++ public boolean generation; ++ public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ @Nullable // Paper - Refresh ProjectileSource for projectiles ++ public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only ++ public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled ++ public boolean persistentInvisibility = false; ++ public BlockPos lastLavaContact; ++ // Marks an entity, that it was removed by a plugin via Entity#remove ++ // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed ++ public boolean pluginRemoved = false; ++ // Spigot start ++ public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); ++ public final boolean defaultActivationState; ++ public long activatedTick = Integer.MIN_VALUE; ++ public void inactiveTick() { } ++ // Spigot end ++ protected int numCollisions = 0; // Paper - Cap entity collisions ++ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals ++ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one ++ // Paper start - Entity origin API ++ @javax.annotation.Nullable ++ private org.bukkit.util.Vector origin; ++ @javax.annotation.Nullable ++ private UUID originWorld; ++ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API ++ public boolean fixedPose = false; // Paper - Expand Pose API ++ private final int despawnTime; // Paper - entity despawn time limit + ++ public void setOrigin(@javax.annotation.Nonnull Location location) { ++ this.origin = location.toVector(); ++ this.originWorld = location.getWorld().getUID(); ++ } ++ ++ @javax.annotation.Nullable ++ public org.bukkit.util.Vector getOriginVector() { ++ return this.origin != null ? this.origin.clone() : null; ++ } ++ ++ @javax.annotation.Nullable ++ public UUID getOriginWorld() { ++ return this.originWorld; ++ } ++ // Paper end - Entity origin API ++ public float getBukkitYaw() { ++ return this.yRot; ++ } ++ ++ public boolean isChunkLoaded() { ++ return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); ++ } ++ // CraftBukkit end ++ // Paper start ++ public final AABB getBoundingBoxAt(double x, double y, double z) { ++ return this.dimensions.makeBoundingBox(x, y, z); ++ } ++ // Paper end ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); ++ this.despawnTime = type == EntityType.PLAYER ? -1 : world.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit + this.passengers = ImmutableList.of(); + this.deltaMovement = Vec3.ZERO; + this.bb = Entity.INITIAL_AABB; + this.stuckSpeedMultiplier = Vec3.ZERO; + this.nextStep = 1.0F; +- this.random = RandomSource.create(); ++ this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random + this.remainingFireTicks = -this.getFireImmuneTicks(); + this.fluidHeight = new Object2DoubleArrayMap(2); + this.fluidOnEyes = new HashSet(); +@@ -270,7 +477,7 @@ + this.packetPositionCodec = new VecDeltaCodec(); + this.uuid = Mth.createInsecureUUID(this.random); + this.stringUUID = this.uuid.toString(); +- this.tags = Sets.newHashSet(); ++ this.tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl + this.pistonDeltas = new double[]{0.0D, 0.0D, 0.0D}; + this.mainSupportingBlockPos = Optional.empty(); + this.onGroundNoBlocks = false; +@@ -284,6 +491,13 @@ + this.position = Vec3.ZERO; + this.blockPosition = BlockPos.ZERO; + this.chunkPosition = ChunkPos.ZERO; ++ // Spigot start ++ if (world != null) { ++ this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); ++ } else { ++ this.defaultActivationState = false; ++ } ++ // Spigot end + SynchedEntityData.Builder datawatcher_a = new SynchedEntityData.Builder(this); + + datawatcher_a.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0); +@@ -292,7 +506,7 @@ + datawatcher_a.define(Entity.DATA_CUSTOM_NAME, Optional.empty()); + datawatcher_a.define(Entity.DATA_SILENT, false); + datawatcher_a.define(Entity.DATA_NO_GRAVITY, false); +- datawatcher_a.define(Entity.DATA_POSE, Pose.STANDING); ++ datawatcher_a.define(Entity.DATA_POSE, net.minecraft.world.entity.Pose.STANDING); + datawatcher_a.define(Entity.DATA_TICKS_FROZEN, 0); + this.defineSynchedData(datawatcher_a); + this.entityData = datawatcher_a.build(); +@@ -354,7 +568,7 @@ + } + + public boolean addTag(String tag) { +- return this.tags.size() >= 1024 ? false : this.tags.add(tag); ++ return this.tags.add(tag); // Paper - fully limit tag size - replace set impl + } + + public boolean removeTag(String tag) { +@@ -362,20 +576,68 @@ + } + + public void kill(ServerLevel world) { +- this.remove(Entity.RemovalReason.KILLED); ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + this.gameEvent(GameEvent.ENTITY_DIE); + } + + public final void discard() { +- this.remove(Entity.RemovalReason.DISCARDED); ++ // CraftBukkit start - add Bukkit remove cause ++ this.discard(null); + } + ++ public final void discard(EntityRemoveEvent.Cause cause) { ++ this.remove(Entity.RemovalReason.DISCARDED, cause); ++ // CraftBukkit end ++ } ++ + protected abstract void defineSynchedData(SynchedEntityData.Builder builder); + + public SynchedEntityData getEntityData() { + return this.entityData; + } + ++ // CraftBukkit start ++ public void refreshEntityData(ServerPlayer to) { ++ List> list = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default ++ ++ if (list != null && to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper ++ to.connection.send(new ClientboundSetEntityDataPacket(this.getId(), list)); ++ } ++ } ++ // CraftBukkit end ++ // Paper start ++ // This method should only be used if the data of an entity could have become desynced ++ // due to interactions on the client. ++ public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) { ++ if (player.getBukkitEntity().canSee(this.getBukkitEntity())) { ++ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level(); ++ net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId()); ++ if (tracker == null) { ++ return; ++ } ++ final net.minecraft.server.level.ServerEntity serverEntity = tracker.serverEntity; ++ final List> list = new java.util.ArrayList<>(); ++ serverEntity.sendPairingData(player, list::add); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list)); ++ } ++ } ++ ++ // This method allows you to specifically resend certain data accessor keys to the client ++ public void resendPossiblyDesyncedDataValues(List> keys, ServerPlayer to) { ++ if (!to.getBukkitEntity().canSee(this.getBukkitEntity())) { ++ return; ++ } ++ ++ final List> values = new java.util.ArrayList<>(keys.size()); ++ for (final EntityDataAccessor key : keys) { ++ final SynchedEntityData.DataItem synchedValue = this.entityData.getItem(key); ++ values.add(synchedValue.value()); ++ } ++ ++ to.connection.send(new ClientboundSetEntityDataPacket(this.id, values)); ++ } ++ // Paper end ++ + public boolean equals(Object object) { + return object instanceof Entity ? ((Entity) object).id == this.id : false; + } +@@ -385,22 +647,39 @@ + } + + public void remove(Entity.RemovalReason reason) { +- this.setRemoved(reason); ++ // CraftBukkit start - add Bukkit remove cause ++ this.setRemoved(reason, null); + } + ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ this.setRemoved(entity_removalreason, cause); ++ // CraftBukkit end ++ } ++ + public void onClientRemoval() {} + + public void onRemoval(Entity.RemovalReason reason) {} + +- public void setPose(Pose pose) { ++ public void setPose(net.minecraft.world.entity.Pose pose) { ++ if (this.fixedPose) return; // Paper - Expand Pose API ++ // CraftBukkit start ++ if (pose == this.getPose()) { ++ return; ++ } ++ // Paper start - Don't fire sync event during generation ++ if (!this.generation) { ++ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ } ++ // Paper end - Don't fire sync event during generation ++ // CraftBukkit end + this.entityData.set(Entity.DATA_POSE, pose); + } + +- public Pose getPose() { +- return (Pose) this.entityData.get(Entity.DATA_POSE); ++ public net.minecraft.world.entity.Pose getPose() { ++ return (net.minecraft.world.entity.Pose) this.entityData.get(Entity.DATA_POSE); + } + +- public boolean hasPose(Pose pose) { ++ public boolean hasPose(net.minecraft.world.entity.Pose pose) { + return this.getPose() == pose; + } + +@@ -417,6 +696,33 @@ + } + + public void setRot(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 ServerPlayer) { ++ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)"); ++ } ++ 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 ServerPlayer) { ++ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)"); ++ } ++ pitch = 0; ++ } ++ // CraftBukkit end ++ + this.setYRot(yaw % 360.0F); + this.setXRot(pitch % 360.0F); + } +@@ -426,8 +732,8 @@ + } + + public void setPos(double x, double y, double z) { +- this.setPosRaw(x, y, z); +- this.setBoundingBox(this.makeBoundingBox()); ++ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update ++ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw + } + + protected final AABB makeBoundingBox() { +@@ -459,13 +765,29 @@ + } + + public void tick() { ++ // Paper start - entity despawn time limit ++ if (this.despawnTime >= 0 && this.tickCount >= this.despawnTime) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); ++ return; ++ } ++ // Paper end - entity despawn time limit + this.baseTick(); + } + ++ // CraftBukkit start ++ 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 instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities ++ this.handlePortal(); ++ } ++ } ++ // CraftBukkit end ++ + public void baseTick() { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("entityBaseTick"); ++ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups + this.inBlockState = null; + if (this.isPassenger() && this.getVehicle().isRemoved()) { + this.stopRiding(); +@@ -475,7 +797,7 @@ + --this.boardingCooldown; + } + +- this.handlePortal(); ++ if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick + if (this.canSpawnSprintParticle()) { + this.spawnSprintParticle(); + } +@@ -502,7 +824,7 @@ + this.setRemainingFireTicks(this.remainingFireTicks - 1); + } + +- if (this.getTicksFrozen() > 0) { ++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API + this.setTicksFrozen(0); + this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); + } +@@ -514,6 +836,10 @@ + if (this.isInLava()) { + this.lavaHurt(); + this.fallDistance *= 0.5F; ++ // CraftBukkit start ++ } else { ++ this.lastLavaContact = null; ++ // CraftBukkit end + } + + this.checkBelowWorld(); +@@ -525,7 +851,7 @@ + world = this.level(); + if (world instanceof ServerLevel worldserver) { + if (this instanceof Leashable) { +- Leashable.tickLeash(worldserver, (Entity) ((Leashable) this)); ++ Leashable.tickLeash(worldserver, (Entity & Leashable) this); // CraftBukkit - decompile error + } + } + +@@ -537,7 +863,12 @@ + } + + public void checkBelowWorld() { +- if (this.getY() < (double) (this.level().getMinY() - 64)) { ++ if (!this.level.getWorld().isVoidDamageEnabled()) return; // Paper - check if void damage is enabled on the world ++ // Paper start - Configurable nether ceiling damage ++ if (this.getY() < (double) (this.level.getMinY() + this.level.getWorld().getVoidDamageMinBuildHeightOffset()) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Paper - use configured min build height offset ++ && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) ++ && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { ++ // Paper end - Configurable nether ceiling damage + this.onBelowWorld(); + } + +@@ -568,15 +899,32 @@ + + public void lavaHurt() { + if (!this.fireImmune()) { +- this.igniteForSeconds(15.0F); ++ // CraftBukkit start - Fallen in lava TODO: this event spams! ++ if (this instanceof net.minecraft.world.entity.LivingEntity && this.remainingFireTicks <= 0) { ++ // not on fire yet ++ org.bukkit.block.Block damager = (this.lastLavaContact == null) ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact); ++ org.bukkit.entity.Entity damagee = this.getBukkitEntity(); ++ EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15); ++ this.level.getCraftServer().getPluginManager().callEvent(combustEvent); ++ ++ if (!combustEvent.isCancelled()) { ++ this.igniteForSeconds(combustEvent.getDuration(), false); ++ } ++ } else { ++ // This will be called every single tick the entity is in lava, so don't throw an event ++ this.igniteForSeconds(15.0F, false); ++ } ++ // CraftBukkit end + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- if (this.hurtServer(worldserver, this.damageSources().lava(), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) { ++ // CraftBukkit start ++ if (this.hurtServer(worldserver, this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) { + worldserver.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.GENERIC_BURN, this.getSoundSource(), 0.4F, 2.0F + this.random.nextFloat() * 0.4F); + } ++ // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls + } + + } +@@ -587,9 +935,25 @@ + } + + public final void igniteForSeconds(float seconds) { +- this.igniteForTicks(Mth.floor(seconds * 20.0F)); ++ // CraftBukkit start ++ this.igniteForSeconds(seconds, true); + } + ++ public final void igniteForSeconds(float f, boolean callEvent) { ++ if (callEvent) { ++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), f); ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ f = event.getDuration(); ++ } ++ // CraftBukkit end ++ this.igniteForTicks(Mth.floor(f * 20.0F)); ++ } ++ + public void igniteForTicks(int ticks) { + if (this.remainingFireTicks < ticks) { + this.setRemainingFireTicks(ticks); +@@ -610,7 +974,7 @@ + } + + protected void onBelowWorld() { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause + } + + public boolean isFree(double offsetX, double offsetY, double offsetZ) { +@@ -672,6 +1036,7 @@ + } + + public void move(MoverType type, Vec3 movement) { ++ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -747,8 +1112,30 @@ + + if (movement.y != vec3d1.y) { + block.updateEntityMovementAfterFallOn(this.level(), this); ++ } ++ } ++ ++ // CraftBukkit start ++ if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) { ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ())); ++ ++ if (movement.x > vec3d1.x) { ++ bl = bl.getRelative(BlockFace.EAST); ++ } else if (movement.x < vec3d1.x) { ++ bl = bl.getRelative(BlockFace.WEST); ++ } else if (movement.z > vec3d1.z) { ++ bl = bl.getRelative(BlockFace.SOUTH); ++ } else if (movement.z < vec3d1.z) { ++ bl = bl.getRelative(BlockFace.NORTH); ++ } ++ ++ if (!bl.getType().isAir()) { ++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity ++ this.level.getCraftServer().getPluginManager().callEvent(event); + } + } ++ // CraftBukkit end + + if (!this.level().isClientSide() || this.isControlledByLocalInstance()) { + Entity.MovementEmission entity_movementemission = this.getMovementEmission(); +@@ -913,7 +1300,7 @@ + } + + protected BlockPos getOnPos(float offset) { +- if (this.mainSupportingBlockPos.isPresent()) { ++ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads + BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); + + if (offset <= 1.0E-5F) { +@@ -1133,6 +1520,20 @@ + return SoundEvents.GENERIC_SPLASH; + } + ++ // CraftBukkit start - Add delegate methods ++ public SoundEvent getSwimSound0() { ++ return this.getSwimSound(); ++ } ++ ++ public SoundEvent getSwimSplashSound0() { ++ return this.getSwimSplashSound(); ++ } ++ ++ public SoundEvent getSwimHighSpeedSplashSound0() { ++ return this.getSwimHighSpeedSplashSound(); ++ } ++ // CraftBukkit end ++ + public void recordMovementThroughBlocks(Vec3 oldPos, Vec3 newPos) { + this.movementThisTick.add(new Entity.Movement(oldPos, newPos)); + } +@@ -1599,6 +2000,7 @@ + this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public void absMoveTo(double x, double y, double z) { +@@ -1609,6 +2011,7 @@ + this.yo = y; + this.zo = d4; + this.setPos(d3, y, d4); ++ if (this.valid) this.level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit + } + + public void moveTo(Vec3 pos) { +@@ -1628,11 +2031,19 @@ + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // Paper start - Fix Entity Teleportation and cancel velocity if teleported ++ if (!preserveMotion) { ++ this.deltaMovement = Vec3.ZERO; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end - Fix Entity Teleportation and cancel velocity if teleported + this.setPosRaw(x, y, z); + this.setYRot(yaw); + this.setXRot(pitch); + this.setOldPosAndRot(); + this.reapplyPosition(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public final void setOldPosAndRot() { +@@ -1701,6 +2112,7 @@ + public void push(Entity entity) { + if (!this.isPassengerOfSameVehicle(entity)) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +@@ -1737,7 +2149,21 @@ + } + + public void push(double deltaX, double deltaY, double deltaZ) { +- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); ++ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent ++ this.push(deltaX, deltaY, deltaZ, null); ++ } ++ ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); ++ if (pushingEntity != null) { ++ io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta); ++ if (!event.callEvent()) { ++ return; ++ } ++ delta = event.getKnockback(); ++ } ++ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); ++ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.hasImpulse = true; + } + +@@ -1858,9 +2284,21 @@ + } + + public boolean isPushable() { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + return false; + } + ++ // CraftBukkit start - collidable API ++ public boolean canCollideWithBukkit(Entity entity) { ++ return this.isPushable(); ++ } ++ // CraftBukkit end ++ + public void awardKillScore(Entity entityKilled, DamageSource damageSource) { + if (entityKilled instanceof ServerPlayer) { + CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource); +@@ -1889,74 +2327,133 @@ + } + + public boolean saveAsPassenger(CompoundTag nbt) { ++ // CraftBukkit start - allow excluding certain data when saving ++ return this.saveAsPassenger(nbt, true); ++ } ++ ++ public boolean saveAsPassenger(CompoundTag nbttagcompound, boolean includeAll) { ++ // CraftBukkit end + if (this.removalReason != null && !this.removalReason.shouldSave()) { + return false; + } else { + String s = this.getEncodeId(); + +- if (s == null) { ++ if (!this.persist || s == null) { // CraftBukkit - persist flag + return false; + } else { +- nbt.putString("id", s); +- this.saveWithoutId(nbt); ++ nbttagcompound.putString("id", s); ++ this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll + return true; + } + } + } + ++ // Paper start - Entity serialization api ++ public boolean serializeEntity(CompoundTag compound) { ++ List pass = new java.util.ArrayList<>(this.getPassengers()); ++ this.passengers = ImmutableList.of(); ++ boolean result = save(compound); ++ this.passengers = ImmutableList.copyOf(pass); ++ return result; ++ } ++ // Paper end - Entity serialization api + public boolean save(CompoundTag nbt) { + return this.isPassenger() ? false : this.saveAsPassenger(nbt); + } + + public CompoundTag saveWithoutId(CompoundTag nbt) { ++ // CraftBukkit start - allow excluding certain data when saving ++ return this.saveWithoutId(nbt, true); ++ } ++ ++ public CompoundTag saveWithoutId(CompoundTag nbttagcompound, boolean includeAll) { ++ // CraftBukkit end + try { +- if (this.vehicle != null) { +- nbt.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ())); +- } else { +- nbt.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ())); ++ // CraftBukkit start - selectively save position ++ if (includeAll) { ++ if (this.vehicle != null) { ++ nbttagcompound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ())); ++ } else { ++ nbttagcompound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ())); ++ } + } ++ // CraftBukkit end + + Vec3 vec3d = this.getDeltaMovement(); + +- nbt.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z)); +- nbt.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot())); +- nbt.putFloat("FallDistance", this.fallDistance); +- nbt.putShort("Fire", (short) this.remainingFireTicks); +- nbt.putShort("Air", (short) this.getAirSupply()); +- nbt.putBoolean("OnGround", this.onGround()); +- nbt.putBoolean("Invulnerable", this.invulnerable); +- nbt.putInt("PortalCooldown", this.portalCooldown); +- nbt.putUUID("UUID", this.getUUID()); ++ nbttagcompound.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z)); ++ ++ // 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.yRot)) { ++ this.yRot = 0; ++ } ++ ++ if (Float.isNaN(this.xRot)) { ++ this.xRot = 0; ++ } ++ // CraftBukkit end ++ ++ nbttagcompound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot())); ++ nbttagcompound.putFloat("FallDistance", this.fallDistance); ++ nbttagcompound.putShort("Fire", (short) this.remainingFireTicks); ++ nbttagcompound.putShort("Air", (short) this.getAirSupply()); ++ nbttagcompound.putBoolean("OnGround", this.onGround()); ++ nbttagcompound.putBoolean("Invulnerable", this.invulnerable); ++ nbttagcompound.putInt("PortalCooldown", this.portalCooldown); ++ // CraftBukkit start - selectively save uuid and world ++ if (includeAll) { ++ nbttagcompound.putUUID("UUID", this.getUUID()); ++ // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast ++ nbttagcompound.putLong("WorldUUIDLeast", ((ServerLevel) this.level).getWorld().getUID().getLeastSignificantBits()); ++ nbttagcompound.putLong("WorldUUIDMost", ((ServerLevel) this.level).getWorld().getUID().getMostSignificantBits()); ++ } ++ nbttagcompound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL); ++ if (!this.persist) { ++ nbttagcompound.putBoolean("Bukkit.persist", this.persist); ++ } ++ if (!this.visibleByDefault) { ++ nbttagcompound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault); ++ } ++ if (this.persistentInvisibility) { ++ nbttagcompound.putBoolean("Bukkit.invisible", this.persistentInvisibility); ++ } ++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ if (this.maxAirTicks != this.getDefaultMaxAirSupply()) { ++ nbttagcompound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply()); ++ } ++ nbttagcompound.putInt("Spigot.ticksLived", this.tickCount); ++ // CraftBukkit end + Component ichatbasecomponent = this.getCustomName(); + + if (ichatbasecomponent != null) { +- nbt.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess())); ++ nbttagcompound.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess())); + } + + if (this.isCustomNameVisible()) { +- nbt.putBoolean("CustomNameVisible", this.isCustomNameVisible()); ++ nbttagcompound.putBoolean("CustomNameVisible", this.isCustomNameVisible()); + } + + if (this.isSilent()) { +- nbt.putBoolean("Silent", this.isSilent()); ++ nbttagcompound.putBoolean("Silent", this.isSilent()); + } + + if (this.isNoGravity()) { +- nbt.putBoolean("NoGravity", this.isNoGravity()); ++ nbttagcompound.putBoolean("NoGravity", this.isNoGravity()); + } + + if (this.hasGlowingTag) { +- nbt.putBoolean("Glowing", true); ++ nbttagcompound.putBoolean("Glowing", true); + } + + int i = this.getTicksFrozen(); + + if (i > 0) { +- nbt.putInt("TicksFrozen", this.getTicksFrozen()); ++ nbttagcompound.putInt("TicksFrozen", this.getTicksFrozen()); + } + + if (this.hasVisualFire) { +- nbt.putBoolean("HasVisualFire", this.hasVisualFire); ++ nbttagcompound.putBoolean("HasVisualFire", this.hasVisualFire); + } + + ListTag nbttaglist; +@@ -1972,10 +2469,10 @@ + nbttaglist.add(StringTag.valueOf(s)); + } + +- nbt.put("Tags", nbttaglist); ++ nbttagcompound.put("Tags", nbttaglist); + } + +- this.addAdditionalSaveData(nbt); ++ this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll + if (this.isVehicle()) { + nbttaglist = new ListTag(); + iterator = this.getPassengers().iterator(); +@@ -1984,17 +2481,44 @@ + Entity entity = (Entity) iterator.next(); + CompoundTag nbttagcompound1 = new CompoundTag(); + +- if (entity.saveAsPassenger(nbttagcompound1)) { ++ if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll + nbttaglist.add(nbttagcompound1); + } + } + + if (!nbttaglist.isEmpty()) { +- nbt.put("Passengers", nbttaglist); ++ nbttagcompound.put("Passengers", nbttaglist); + } + } + +- return nbt; ++ // CraftBukkit start - stores eventually existing bukkit values ++ if (this.bukkitEntity != null) { ++ this.bukkitEntity.storeBukkitValues(nbttagcompound); ++ } ++ // CraftBukkit end ++ // Paper start ++ if (this.origin != null) { ++ UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null; ++ if (originWorld != null) { ++ nbttagcompound.putUUID("Paper.OriginWorld", originWorld); ++ } ++ nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); ++ } ++ if (spawnReason != null) { ++ nbttagcompound.putString("Paper.SpawnReason", spawnReason.name()); ++ } ++ // Save entity's from mob spawner status ++ if (spawnedViaMobSpawner) { ++ nbttagcompound.putBoolean("Paper.FromMobSpawner", true); ++ } ++ if (fromNetherPortal) { ++ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); ++ } ++ if (freezeLocked) { ++ nbttagcompound.putBoolean("Paper.FreezeLock", true); ++ } ++ // Paper end ++ return nbttagcompound; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being saved"); +@@ -2080,6 +2604,71 @@ + } else { + throw new IllegalStateException("Entity has invalid position"); + } ++ ++ // CraftBukkit start ++ // Spigot start ++ if (this instanceof net.minecraft.world.entity.LivingEntity) { ++ this.tickCount = nbt.getInt("Spigot.ticksLived"); ++ } ++ // Spigot end ++ this.persist = !nbt.contains("Bukkit.persist") || nbt.getBoolean("Bukkit.persist"); ++ this.visibleByDefault = !nbt.contains("Bukkit.visibleByDefault") || nbt.getBoolean("Bukkit.visibleByDefault"); ++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ if (nbt.contains("Bukkit.MaxAirSupply")) { ++ this.maxAirTicks = nbt.getInt("Bukkit.MaxAirSupply"); ++ } ++ // CraftBukkit end ++ ++ // CraftBukkit start ++ // Paper - move world parsing/loading to PlayerList#placeNewPlayer ++ this.getBukkitEntity().readBukkitValues(nbt); ++ if (nbt.contains("Bukkit.invisible")) { ++ boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); ++ this.setInvisible(bukkitInvisible); ++ this.persistentInvisibility = bukkitInvisible; ++ } ++ // CraftBukkit end ++ ++ // Paper start ++ ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE); ++ if (!originTag.isEmpty()) { ++ UUID originWorld = null; ++ if (nbt.contains("Paper.OriginWorld")) { ++ originWorld = nbt.getUUID("Paper.OriginWorld"); ++ } else if (this.level != null) { ++ originWorld = this.level.getWorld().getUID(); ++ } ++ this.originWorld = originWorld; ++ origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); ++ } ++ ++ spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); ++ if (nbt.contains("Paper.SpawnReason")) { ++ String spawnReasonName = nbt.getString("Paper.SpawnReason"); ++ try { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this); ++ } ++ } ++ if (spawnReason == null) { ++ if (spawnedViaMobSpawner) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; ++ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { ++ if (!nbt.getBoolean("PersistenceRequired")) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; ++ } ++ } ++ } ++ if (spawnReason == null) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; ++ } ++ if (nbt.contains("Paper.FreezeLock")) { ++ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ } ++ // Paper end ++ + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); +@@ -2099,7 +2688,13 @@ + ResourceLocation minecraftkey = EntityType.getKey(entitytypes); + + return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null; ++ } ++ ++ // CraftBukkit start - allow excluding certain data when saving ++ protected void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) { ++ this.addAdditionalSaveData(nbttagcompound); + } ++ // CraftBukkit end + + protected abstract void readAdditionalSaveData(CompoundTag nbt); + +@@ -2150,12 +2745,60 @@ + + @Nullable + public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset) { ++ // Paper start - Restore vanilla drops behavior ++ return this.spawnAtLocation(world, stack, yOffset, null); ++ } ++ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { ++ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { ++ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); ++ } ++ ++ public void runConsumer(final java.util.function.Consumer fallback) { ++ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) { ++ fallback.accept(this.stack); ++ } else { ++ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); ++ } ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { ++ // Paper end - Restore vanilla drops behavior + if (stack.isEmpty()) { + return null; + } else { +- ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ // CraftBukkit start - Capture drops for death event ++ if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { ++ // Paper start - Restore vanilla drops behavior ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { ++ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer ++ itemEntity.setDefaultPickUpDelay(); ++ this.level.addFreshEntity(itemEntity); ++ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); ++ })); ++ // Paper end - Restore vanilla drops behavior ++ return null; ++ } ++ // CraftBukkit end ++ ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original ++ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + +- entityitem.setDefaultPickUpDelay(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) ++ // Paper start - Call EntityDropItemEvent ++ return this.spawnAtLocation(world, entityitem); ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ServerLevel world, ItemEntity entityitem) { ++ { ++ // Paper end - Call EntityDropItemEvent ++ // CraftBukkit start ++ EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return null; ++ } ++ // CraftBukkit end + world.addFreshEntity(entityitem); + return entityitem; + } +@@ -2184,7 +2827,16 @@ + if (this.isAlive() && this instanceof Leashable leashable) { + if (leashable.getLeashHolder() == player) { + if (!this.level().isClientSide()) { +- if (player.hasInfiniteMaterials()) { ++ // CraftBukkit start - fire PlayerUnleashEntityEvent ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent ++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end ++ if (!event.isDropLeash()) { // Paper - Expand EntityUnleashEvent + leashable.removeLeash(); + } else { + leashable.dropLeash(); +@@ -2200,6 +2852,14 @@ + + if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) { + if (!this.level().isClientSide()) { ++ // CraftBukkit start - fire PlayerLeashEntityEvent ++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) { ++ // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync ++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + leashable.setLeashedTo(player, true); + } + +@@ -2265,15 +2925,15 @@ + } + + public boolean showVehicleHealth() { +- return this instanceof LivingEntity; ++ return this instanceof net.minecraft.world.entity.LivingEntity; + } + + public boolean startRiding(Entity entity, boolean force) { +- if (entity == this.vehicle) { ++ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) + return false; + } else if (!entity.couldAcceptPassenger()) { + return false; +- } else if (!this.level().isClientSide() && !entity.type.canSerialize()) { ++ } else if (!force && !this.level().isClientSide() && !entity.type.canSerialize()) { // SPIGOT-7947: Allow force riding all entities + return false; + } else { + for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) { +@@ -2285,11 +2945,32 @@ + if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) { + return false; + } else { ++ // CraftBukkit start ++ if (entity.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof LivingEntity) { ++ VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) entity.getBukkitEntity(), this.getBukkitEntity()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ } ++ ++ EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), entity.getBukkitEntity()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + if (this.isPassenger()) { + this.stopRiding(); + } + +- this.setPose(Pose.STANDING); ++ this.setPose(net.minecraft.world.entity.Pose.STANDING); + this.vehicle = entity; + this.vehicle.addPassenger(this); + entity.getIndirectPassengersStream().filter((entity2) -> { +@@ -2314,19 +2995,30 @@ + } + + public void removeVehicle() { ++ // Paper start - Force entity dismount during teleportation ++ this.removeVehicle(false); ++ } ++ public void removeVehicle(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + if (this.vehicle != null) { + Entity entity = this.vehicle; + + this.vehicle = null; +- entity.removePassenger(this); ++ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation + } + + } + + public void stopRiding() { +- this.removeVehicle(); ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); + } + ++ public void stopRiding(boolean suppressCancellation) { ++ this.removeVehicle(suppressCancellation); ++ // Paper end - Force entity dismount during teleportation ++ } ++ + protected void addPassenger(Entity passenger) { + if (passenger.getVehicle() != this) { + throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); +@@ -2349,21 +3041,53 @@ + } + } + +- protected void removePassenger(Entity passenger) { +- if (passenger.getVehicle() == this) { ++ // Paper start - Force entity dismount during teleportation ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end - Force entity dismount during teleportation ++ if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +- if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) { ++ // CraftBukkit start ++ CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); ++ Entity orig = craft == null ? null : craft.getHandle(); ++ if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { ++ VehicleExitEvent event = new VehicleExitEvent( ++ (Vehicle) this.getBukkitEntity(), ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation ++ ); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle(); ++ Entity n = craftn == null ? null : craftn.getHandle(); ++ if (event.isCancelled() || n != orig) { ++ return false; ++ } ++ } ++ ++ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end ++ if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { + this.passengers = ImmutableList.of(); + } else { + this.passengers = (ImmutableList) this.passengers.stream().filter((entity1) -> { +- return entity1 != passenger; ++ return entity1 != entity; + }).collect(ImmutableList.toImmutableList()); + } + +- passenger.boardingCooldown = 60; +- this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); ++ entity.boardingCooldown = 60; ++ this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity); + } ++ return true; // CraftBukkit + } + + protected boolean canAddPassenger(Entity passenger) { +@@ -2464,7 +3188,7 @@ + if (teleporttransition != null) { + ServerLevel worldserver1 = teleporttransition.newLevel(); + +- if (worldserver.getServer().isLevelEnabled(worldserver1) && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1))) { ++ if (this instanceof ServerPlayer || (worldserver1 != null && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1)))) { // CraftBukkit - always call event for players + this.teleport(teleporttransition); + } + } +@@ -2547,7 +3271,7 @@ + } + + public boolean isCrouching() { +- return this.hasPose(Pose.CROUCHING); ++ return this.hasPose(net.minecraft.world.entity.Pose.CROUCHING); + } + + public boolean isSprinting() { +@@ -2563,7 +3287,7 @@ + } + + public boolean isVisuallySwimming() { +- return this.hasPose(Pose.SWIMMING); ++ return this.hasPose(net.minecraft.world.entity.Pose.SWIMMING); + } + + public boolean isVisuallyCrawling() { +@@ -2571,6 +3295,13 @@ + } + + public void setSwimming(boolean swimming) { ++ // CraftBukkit start ++ if (this.valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) { ++ if (CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + this.setSharedFlag(4, swimming); + } + +@@ -2609,6 +3340,7 @@ + + @Nullable + public PlayerTeam getTeam() { ++ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default + return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); + } + +@@ -2624,8 +3356,12 @@ + return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false; + } + ++ // CraftBukkit - start + public void setInvisible(boolean invisible) { +- this.setSharedFlag(5, invisible); ++ if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag ++ this.setSharedFlag(5, invisible); ++ } ++ // CraftBukkit - end + } + + public boolean getSharedFlag(int index) { +@@ -2644,7 +3380,7 @@ + } + + public int getMaxAirSupply() { +- return 300; ++ return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + } + + public int getAirSupply() { +@@ -2652,7 +3388,18 @@ + } + + public void setAirSupply(int air) { +- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, air); ++ // CraftBukkit start ++ EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air); ++ // Suppress during worldgen ++ if (this.valid) { ++ event.getEntity().getServer().getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled() && this.getAirSupply() != air) { ++ this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID); ++ return; ++ } ++ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount()); ++ // CraftBukkit end + } + + public int getTicksFrozen() { +@@ -2679,11 +3426,44 @@ + + public void thunderHit(ServerLevel world, LightningBolt lightning) { + this.setRemainingFireTicks(this.remainingFireTicks + 1); ++ // CraftBukkit start ++ final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity(); ++ final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity(); ++ final PluginManager pluginManager = Bukkit.getPluginManager(); ++ // CraftBukkit end ++ + if (this.remainingFireTicks == 0) { +- this.igniteForSeconds(8.0F); ++ // CraftBukkit start - Call a combust event when lightning strikes ++ EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F); ++ pluginManager.callEvent(entityCombustEvent); ++ if (!entityCombustEvent.isCancelled()) { ++ this.igniteForSeconds(entityCombustEvent.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ this.setRemainingFireTicks(this.remainingFireTicks - 1); ++ // Paper end - fix EntityCombustEvent cancellation ++ } ++ // CraftBukkit end + } + +- this.hurtServer(world, this.damageSources().lightningBolt(), 5.0F); ++ // CraftBukkit start ++ if (thisBukkitEntity instanceof Hanging) { ++ HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); ++ pluginManager.callEvent(hangingEvent); ++ ++ if (hangingEvent.isCancelled()) { ++ return; ++ } ++ } ++ ++ if (this.fireImmune()) { ++ return; ++ } ++ ++ if (!this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API ++ return; ++ } ++ // CraftBukkit end + } + + public void onAboveBubbleCol(boolean drag) { +@@ -2713,7 +3493,7 @@ + this.resetFallDistance(); + } + +- public boolean killedEntity(ServerLevel world, LivingEntity other) { ++ public boolean killedEntity(ServerLevel world, net.minecraft.world.entity.LivingEntity other) { + return true; + } + +@@ -2818,7 +3598,7 @@ + public String toString() { + String s = this.level() == null ? "~NULL~" : this.level().toString(); + +- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); ++ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info + } + + public final boolean isInvulnerableToBase(DamageSource damageSource) { +@@ -2838,6 +3618,13 @@ + } + + public void restoreFrom(Entity original) { ++ // Paper start - Forward CraftEntity in teleport command ++ CraftEntity bukkitEntity = original.bukkitEntity; ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(this); ++ this.bukkitEntity = bukkitEntity; ++ } ++ // Paper end - Forward CraftEntity in teleport command + CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); + + nbttagcompound.remove("Dimension"); +@@ -2850,8 +3637,57 @@ + public Entity teleport(TeleportTransition teleportTarget) { + Level world = this.level(); + ++ // Paper start - Fix item duplication and teleport issues ++ if ((!this.isAlive() || !this.valid) && (teleportTarget.newLevel() != world)) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.position(), new Throwable()); ++ return null; ++ } ++ // Paper end - Fix item duplication and teleport issues + if (world instanceof ServerLevel worldserver) { + if (!this.isRemoved()) { ++ // CraftBukkit start ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ Vec3 velocity = absolutePosition.deltaMovement(); // Paper ++ Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ // Paper start - gateway-specific teleport event ++ final EntityTeleportEvent teleEvent; ++ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { ++ teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity)); ++ teleEvent.callEvent(); ++ } else { ++ teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to); ++ } ++ // Paper end - gateway-specific teleport event ++ if (teleEvent.isCancelled() || teleEvent.getTo() == null) { ++ return null; ++ } ++ if (!to.equals(teleEvent.getTo())) { ++ to = teleEvent.getTo(); ++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ // Paper start - Call EntityPortalExitEvent ++ velocity = Vec3.ZERO; ++ } ++ if (this.portalProcess != null) { // if in a portal ++ CraftEntity bukkitEntity = this.getBukkitEntity(); ++ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent( ++ bukkitEntity, ++ bukkitEntity.getLocation(), to.clone(), ++ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity) ++ ); ++ event.callEvent(); ++ ++ // Only change the target if actually needed, since we reset relative flags ++ if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) { ++ to = event.getTo().clone(); ++ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); ++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ } ++ } ++ if (this.isRemoved()) { ++ return null; ++ } ++ // Paper end - Call EntityPortalExitEvent ++ // CraftBukkit end + ServerLevel worldserver1 = teleportTarget.newLevel(); + boolean flag = worldserver1.dimension() != worldserver.dimension(); + +@@ -2918,10 +3754,19 @@ + gameprofilerfiller.pop(); + return null; + } else { ++ // Paper start - Fix item duplication and teleport issues ++ if (this instanceof Leashable leashable) { ++ leashable.dropLeash(); // Paper drop lead ++ } ++ // Paper end - Fix item duplication and teleport issues + entity.restoreFrom(this); + this.removeAfterChangingDimensions(); ++ // CraftBukkit start - Forward the CraftEntity to the new entity ++ //this.getBukkitEntity().setHandle(entity); ++ //entity.bukkitEntity = this.getBukkitEntity(); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom ++ // CraftBukkit end + entity.teleportSetPosition(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); +- world.addDuringTeleport(entity); ++ if (this.inWorld) world.addDuringTeleport(entity); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned + Iterator iterator1 = list1.iterator(); + + while (iterator1.hasNext()) { +@@ -2947,7 +3792,7 @@ + } + + private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTarget) { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + Iterator iterator = this.getIndirectPassengers().iterator(); + + while (iterator.hasNext()) { +@@ -2995,9 +3840,17 @@ + } + + protected void removeAfterChangingDimensions() { +- this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION); +- if (this instanceof Leashable leashable) { +- leashable.removeLeash(); ++ this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause ++ if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit ++ event.callEvent(); ++ if (!event.isDropLeash()) { ++ leashable.removeLeash(); ++ } else { ++ leashable.dropLeash(); ++ } ++ // Paper end - Expand EntityUnleashEvent + } + + } +@@ -3005,12 +3858,35 @@ + public Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) { + return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose())); + } ++ ++ // CraftBukkit start ++ public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) { ++ org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); ++ Location enter = bukkitEntity.getLocation(); ++ ++ // Paper start ++ final org.bukkit.PortalType portalType = switch (cause) { ++ case END_PORTAL -> org.bukkit.PortalType.ENDER; ++ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER; ++ case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet ++ default -> org.bukkit.PortalType.CUSTOM; ++ }; ++ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType); ++ // Paper end ++ event.getEntity().getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) { ++ return null; ++ } ++ return new CraftPortalEvent(event); ++ } ++ // CraftBukkit end + + public boolean canUsePortal(boolean allowVehicles) { + return (allowVehicles || !this.isPassenger()) && this.isAlive(); + } + + public boolean canTeleport(Level from, Level to) { ++ if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues + if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { + Iterator iterator = this.getPassengers().iterator(); + +@@ -3134,10 +4010,16 @@ + return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE); + } + +- public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set flags, float yaw, float pitch, boolean resetCamera) { +- float f2 = Mth.clamp(pitch, -90.0F, 90.0F); +- Entity entity = this.teleport(new TeleportTransition(world, new Vec3(destX, destY, destZ), Vec3.ZERO, yaw, f2, flags, TeleportTransition.DO_NOTHING)); ++ // CraftBukkit start ++ public final boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set flags, float yaw, float pitch, boolean resetCamera) { ++ return this.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } + ++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set set, float f, float f1, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ float f2 = Mth.clamp(f1, -90.0F, 90.0F); ++ Entity entity = this.teleport(new TeleportTransition(worldserver, new Vec3(d0, d1, d2), Vec3.ZERO, f, f2, set, TeleportTransition.DO_NOTHING, cause)); ++ // CraftBukkit end ++ + return entity != null; + } + +@@ -3187,7 +4069,7 @@ + /** @deprecated */ + @Deprecated + protected void fixupDimensions() { +- Pose entitypose = this.getPose(); ++ net.minecraft.world.entity.Pose entitypose = this.getPose(); + EntityDimensions entitysize = this.getDimensions(entitypose); + + this.dimensions = entitysize; +@@ -3196,7 +4078,7 @@ + + public void refreshDimensions() { + EntityDimensions entitysize = this.dimensions; +- Pose entitypose = this.getPose(); ++ net.minecraft.world.entity.Pose entitypose = this.getPose(); + EntityDimensions entitysize1 = this.getDimensions(entitypose); + + this.dimensions = entitysize1; +@@ -3258,10 +4140,29 @@ + } + + public final void setBoundingBox(AABB boundingBox) { +- this.bb = boundingBox; ++ // CraftBukkit start - block invalid bounding boxes ++ double minX = boundingBox.minX, ++ minY = boundingBox.minY, ++ minZ = boundingBox.minZ, ++ maxX = boundingBox.maxX, ++ maxY = boundingBox.maxY, ++ maxZ = boundingBox.maxZ; ++ double len = boundingBox.maxX - boundingBox.minX; ++ if (len < 0) maxX = minX; ++ if (len > 64) maxX = minX + 64.0; ++ ++ len = boundingBox.maxY - boundingBox.minY; ++ if (len < 0) maxY = minY; ++ if (len > 64) maxY = minY + 64.0; ++ ++ len = boundingBox.maxZ - boundingBox.minZ; ++ if (len < 0) maxZ = minZ; ++ if (len > 64) maxZ = minZ + 64.0; ++ this.bb = new AABB(minX, minY, minZ, maxX, maxY, maxZ); ++ // CraftBukkit end + } + +- public final float getEyeHeight(Pose pose) { ++ public final float getEyeHeight(net.minecraft.world.entity.Pose pose) { + return this.getDimensions(pose).eyeHeight(); + } + +@@ -3300,7 +4201,14 @@ + + public void startSeenByPlayer(ServerPlayer player) {} + +- public void stopSeenByPlayer(ServerPlayer player) {} ++ // Paper start - entity tracking events ++ public void stopSeenByPlayer(ServerPlayer player) { ++ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks" ++ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); ++ } ++ } ++ // Paper end - entity tracking events + + public float rotate(Rotation rotation) { + float f = Mth.wrapDegrees(this.getYRot()); +@@ -3335,7 +4243,7 @@ + } + + @Nullable +- public LivingEntity getControllingPassenger() { ++ public net.minecraft.world.entity.LivingEntity getControllingPassenger() { + return null; + } + +@@ -3373,20 +4281,34 @@ + } + + private Stream getIndirectPassengersStream() { ++ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration + return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); + } + + @Override + public Stream getSelfAndPassengers() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); + } + + @Override + public Stream getPassengersAndSelf() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); + } + + public Iterable getIndirectPassengers() { ++ // Paper start - Optimize indirect passenger iteration ++ if (this.passengers.isEmpty()) { return ImmutableList.of(); } ++ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); ++ for (Entity passenger : this.passengers) { ++ indirectPassengers.add(passenger); ++ indirectPassengers.addAll(passenger.getIndirectPassengers()); ++ } ++ return indirectPassengers.build(); ++ } ++ private Iterable getIndirectPassengers_old() { ++ // Paper end - Optimize indirect passenger iteration + return () -> { + return this.getIndirectPassengersStream().iterator(); + }; +@@ -3399,6 +4321,7 @@ + } + + public boolean hasExactlyOnePlayerPassenger() { ++ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration + return this.countPlayerPassengers() == 1; + } + +@@ -3435,7 +4358,7 @@ + } + + public boolean isControlledByLocalInstance() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + if (entityliving instanceof Player entityhuman) { + return entityhuman.isLocalPlayer(); +@@ -3445,7 +4368,7 @@ + } + + public boolean isControlledByClient() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + return entityliving != null && entityliving.isControlledByClient(); + } +@@ -3463,7 +4386,7 @@ + return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3); + } + +- public Vec3 getDismountLocationForPassenger(LivingEntity passenger) { ++ public Vec3 getDismountLocationForPassenger(net.minecraft.world.entity.LivingEntity passenger) { + return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ()); + } + +@@ -3488,9 +4411,38 @@ + public int getFireImmuneTicks() { + return 1; + } ++ ++ // CraftBukkit start ++ private final CommandSource commandSource = new CommandSource() { ++ ++ @Override ++ public void sendSystemMessage(Component message) { ++ } + ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return Entity.this.getBukkitEntity(); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return ((ServerLevel) Entity.this.level()).getGameRules().getBoolean(GameRules.RULE_SENDCOMMANDFEEDBACK); ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return true; ++ } ++ }; ++ // CraftBukkit end ++ + public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel world) { +- return new CommandSourceStack(CommandSource.NULL, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this); ++ return new CommandSourceStack(this.commandSource, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this); // CraftBukkit + } + + public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) { +@@ -3551,6 +4503,11 @@ + vec3d = vec3d.add(vec3d1); + ++k1; + } ++ // CraftBukkit start - store last lava contact location ++ if (tag == FluidTags.LAVA) { ++ this.lastLavaContact = blockposition_mutableblockposition.immutable(); ++ } ++ // CraftBukkit end + } + } + } +@@ -3613,7 +4570,7 @@ + return new ClientboundAddEntityPacket(this, entityTrackerEntry); + } + +- public EntityDimensions getDimensions(Pose pose) { ++ public EntityDimensions getDimensions(net.minecraft.world.entity.Pose pose) { + return this.type.getDimensions(); + } + +@@ -3713,8 +4670,40 @@ + public double getRandomZ(double widthScale) { + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } ++ ++ // Paper start - Block invalid positions and bounding box ++ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { ++ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { ++ return true; ++ } + ++ String entityInfo; ++ try { ++ entityInfo = entity.toString(); ++ } catch (Exception ex) { ++ entityInfo = "[Entity info unavailable] "; ++ } ++ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable()); ++ return false; ++ } + public final void setPosRaw(double x, double y, double z) { ++ this.setPosRaw(x, y, z, false); ++ } ++ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ if (!checkPosition(this, x, y, z)) { ++ return; ++ } ++ // Paper end - Block invalid positions and bounding box ++ // Paper start - Fix MC-4 ++ if (this instanceof ItemEntity) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { ++ // encode/decode from ClientboundMoveEntityPacket ++ x = Mth.lfloor(x * 4096.0) * (1 / 4096.0); ++ y = Mth.lfloor(y * 4096.0) * (1 / 4096.0); ++ z = Mth.lfloor(z * 4096.0) * (1 / 4096.0); ++ } ++ } ++ // Paper end - Fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); +@@ -3732,6 +4721,12 @@ + this.levelCallback.onMove(); + } + ++ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB ++ // hanging has its own special logic ++ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { ++ this.setBoundingBox(this.makeBoundingBox()); ++ } ++ // Paper end - Block invalid positions and bounding box + } + + public void checkDespawn() {} +@@ -3818,8 +4813,17 @@ + + @Override + public final void setRemoved(Entity.RemovalReason reason) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.setRemoved(reason, null); ++ } ++ ++ @Override ++ public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ CraftEventFactory.callEntityRemoveEvent(this, cause); ++ // CraftBukkit end ++ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { +- this.removalReason = reason; ++ this.removalReason = entity_removalreason; + } + + if (this.removalReason.shouldDestroy()) { +@@ -3827,14 +4831,30 @@ + } + + this.getPassengers().forEach(Entity::stopRiding); +- this.levelCallback.onRemove(reason); +- this.onRemoval(reason); ++ this.levelCallback.onRemove(entity_removalreason); ++ this.onRemoval(entity_removalreason); ++ // Paper start - Folia schedulers ++ if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { ++ // Players need to be special cased, because they are regularly removed from the world ++ this.retireScheduler(); ++ } ++ // Paper end - Folia schedulers + } + + public void unsetRemoved() { + this.removalReason = null; + } + ++ // Paper start - Folia schedulers ++ /** ++ * Invoked only when the entity is truly removed from the server, never to be added to any world. ++ */ ++ public final void retireScheduler() { ++ // we need to force create the bukkit entity so that the scheduler can be retired... ++ this.getBukkitEntity().taskScheduler.retire(); ++ } ++ // Paper end - Folia schedulers ++ + @Override + public void setLevelCallback(EntityInLevelCallback changeListener) { + this.levelCallback = changeListener; +@@ -3887,7 +4907,7 @@ + } + + public Vec3 getKnownMovement() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + if (entityliving instanceof Player entityhuman) { + if (this.isAlive()) { +@@ -3962,4 +4982,14 @@ + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start - Expose entity id counter ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.incrementAndGet(); ++ } ++ ++ public boolean isTicking() { ++ return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); ++ } ++ // Paper end - Expose entity id counter + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch new file mode 100644 index 0000000000..e79a8f240b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/entity/EntitySelector.java ++++ b/net/minecraft/world/entity/EntitySelector.java +@@ -27,8 +27,25 @@ + }; + public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); + public static final Predicate CAN_BE_PICKED = EntitySelector.NO_SPECTATORS.and(Entity::isPickable); ++ // Paper start - Ability to control player's insomnia and phantoms ++ public static Predicate IS_INSOMNIAC = (player) -> { ++ net.minecraft.server.level.ServerPlayer serverPlayer = (net.minecraft.server.level.ServerPlayer) player; ++ int playerInsomniaTicks = serverPlayer.level().paperConfig().entities.behavior.playerInsomniaStartTicks; + ++ if (playerInsomniaTicks <= 0) { ++ return false; ++ } ++ ++ return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; ++ }; ++ // Paper end - Ability to control player's insomnia and phantoms ++ + private EntitySelector() {} ++ // Paper start - Affects Spawning API ++ public static final Predicate PLAYER_AFFECTS_SPAWNING = (entity) -> { ++ return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning; ++ }; ++ // Paper end - Affects Spawning API + + public static Predicate withinDistance(double x, double y, double z, double max) { + double d4 = max * max; +@@ -39,13 +56,18 @@ + } + + public static Predicate pushableBy(Entity entity) { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return pushable(entity, false); ++ } ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + PlayerTeam scoreboardteam = entity.getTeam(); + Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { +- if (!entity1.isPushable()) { ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule + return false; +- } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { ++ } else if (entity1 instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision + return false; + } else { + PlayerTeam scoreboardteam1 = entity1.getTeam(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch new file mode 100644 index 0000000000..1ff8d4806a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch @@ -0,0 +1,179 @@ +--- a/net/minecraft/world/entity/EntityType.java ++++ b/net/minecraft/world/entity/EntityType.java +@@ -176,6 +176,7 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.entity.CreatureSpawnEvent; + import org.slf4j.Logger; + + public class EntityType implements FeatureElement, EntityTypeTest { +@@ -191,7 +192,7 @@ + return Items.ACACIA_CHEST_BOAT; + }), MobCategory.MISC).noLootTable().sized(1.375F, 0.5625F).eyeHeight(0.5625F).clientTrackingRange(10)); + public static final EntityType ALLAY = EntityType.register("allay", EntityType.Builder.of(Allay::new, MobCategory.CREATURE).sized(0.35F, 0.6F).eyeHeight(0.36F).ridingOffset(0.04F).clientTrackingRange(8).updateInterval(2)); +- public static final EntityType AREA_EFFECT_CLOUD = EntityType.register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).noLootTable().fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(Integer.MAX_VALUE)); ++ public static final EntityType AREA_EFFECT_CLOUD = EntityType.register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).noLootTable().fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(10)); // CraftBukkit - SPIGOT-3729: track area effect clouds + public static final EntityType ARMADILLO = EntityType.register("armadillo", EntityType.Builder.of(Armadillo::new, MobCategory.CREATURE).sized(0.7F, 0.65F).eyeHeight(0.26F).clientTrackingRange(10)); + public static final EntityType ARMOR_STAND = EntityType.register("armor_stand", EntityType.Builder.of(ArmorStand::new, MobCategory.MISC).sized(0.5F, 1.975F).eyeHeight(1.7775F).clientTrackingRange(10)); + public static final EntityType ARROW = EntityType.register("arrow", EntityType.Builder.of(Arrow::new, MobCategory.MISC).noLootTable().sized(0.5F, 0.5F).eyeHeight(0.13F).clientTrackingRange(4).updateInterval(20)); +@@ -399,7 +400,7 @@ + return ResourceKey.create(Registries.ENTITY_TYPE, ResourceLocation.withDefaultNamespace(id)); + } + +- private static EntityType register(String id, EntityType.Builder type) { ++ private static EntityType register(String id, EntityType.Builder type) { // CraftBukkit - decompile error + return EntityType.register(EntityType.vanillaEntityId(id), type); + } + +@@ -431,16 +432,23 @@ + + @Nullable + public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, EntitySpawnReason spawnReason, boolean alignPosition, boolean invertY) { +- Consumer consumer; ++ // CraftBukkit start ++ return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == EntitySpawnReason.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs ++ } + +- if (stack != null) { +- consumer = EntityType.createDefaultStackConfig(world, stack, player); ++ @Nullable ++ public T spawn(ServerLevel worldserver, @Nullable ItemStack itemstack, @Nullable Player entityhuman, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // CraftBukkit end ++ Consumer consumer; // CraftBukkit - decompile error ++ ++ if (itemstack != null) { ++ consumer = EntityType.createDefaultStackConfig(worldserver, itemstack, entityhuman); + } else { + consumer = (entity) -> { + }; + } + +- return this.spawn(world, consumer, pos, spawnReason, alignPosition, invertY); ++ return this.spawn(worldserver, consumer, blockposition, entityspawnreason, flag, flag1, spawnReason); // CraftBukkit + } + + public static Consumer createDefaultStackConfig(Level world, ItemStack stack, @Nullable Player player) { +@@ -464,21 +472,50 @@ + CustomData customdata = (CustomData) stack.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY); + + return !customdata.isEmpty() ? chained.andThen((entity) -> { +- EntityType.updateCustomEntityTag(world, player, entity, customdata); ++ try { EntityType.updateCustomEntityTag(world, player, entity, customdata); } catch (Throwable t) { EntityType.LOGGER.warn("Error loading spawn egg NBT", t); } // CraftBukkit - SPIGOT-5665 + }) : chained; + } + + @Nullable + public T spawn(ServerLevel world, BlockPos pos, EntitySpawnReason reason) { +- return this.spawn(world, (Consumer) null, pos, reason, false, false); ++ // CraftBukkit start ++ return this.spawn(world, pos, reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); + } + + @Nullable ++ public T spawn(ServerLevel worldserver, BlockPos blockposition, EntitySpawnReason entityspawnreason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ return this.spawn(worldserver, (Consumer) null, blockposition, entityspawnreason, false, false, spawnReason); // CraftBukkit - decompile error ++ // CraftBukkit end ++ } ++ ++ @Nullable + public T spawn(ServerLevel world, @Nullable Consumer afterConsumer, BlockPos pos, EntitySpawnReason reason, boolean alignPosition, boolean invertY) { +- T t0 = this.create(world, afterConsumer, pos, reason, alignPosition, invertY); ++ // CraftBukkit start ++ return this.spawn(world, afterConsumer, pos, reason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ @Nullable ++ public T spawn(ServerLevel worldserver, @Nullable Consumer consumer, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // CraftBukkit end ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(this), ++ spawnReason ++ ); ++ if (!event.callEvent()) { ++ return null; ++ } ++ // Paper end - PreCreatureSpawnEvent ++ T t0 = this.create(worldserver, consumer, blockposition, entityspawnreason, flag, flag1); + + if (t0 != null) { +- world.addFreshEntityWithPassengers(t0); ++ // CraftBukkit start ++ worldserver.addFreshEntityWithPassengers(t0, spawnReason); ++ if (t0.isRemoved()) { ++ return null; // Don't return an entity when CreatureSpawnEvent is canceled ++ } ++ // CraftBukkit end + if (t0 instanceof Mob) { + Mob entityinsentient = (Mob) t0; + +@@ -542,6 +579,15 @@ + + if (entity.getType() == entitytypes) { + if (world.isClientSide || !entity.getType().onlyOpCanSetNbt() || player != null && minecraftserver.getPlayerList().isOp(player.getGameProfile())) { ++ // Paper start - filter out protected tags ++ if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) { ++ nbt = nbt.update((compound) -> { ++ for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : world.paperConfig().entities.spawning.filteredEntityTagNbtPaths) { ++ tag.remove(compound); ++ } ++ }); ++ } ++ // Paper end - filter out protected tags + nbt.loadInto(entity); + } + } +@@ -613,9 +659,15 @@ + } + + public static Optional create(CompoundTag nbt, Level world, EntitySpawnReason reason) { ++ // Paper start - Don't fire sync event during generation ++ return create(nbt, world, reason, false); ++ } ++ public static Optional create(CompoundTag nbt, Level world, EntitySpawnReason reason, boolean generation) { ++ // Paper end - Don't fire sync event during generation + return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> { + return entitytypes.create(world, reason); + }), (entity) -> { ++ if (generation) entity.generation = true; // Paper - Don't fire sync event during generation + entity.load(nbt); + }, () -> { + EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); +@@ -638,7 +690,7 @@ + } + + public static Optional> by(CompoundTag nbt) { +- return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(nbt.getString("id"))); ++ return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation + } + + @Nullable +@@ -657,7 +709,7 @@ + } + + return entity; +- }).orElse((Object) null); ++ }).orElse(null); // CraftBukkit - decompile error + } + + public static Stream loadEntitiesRecursive(final List entityNbtList, final Level world, final EntitySpawnReason reason) { +@@ -718,7 +770,7 @@ + + @Nullable + public T tryCast(Entity obj) { +- return obj.getType() == this ? obj : null; ++ return obj.getType() == this ? (T) obj : null; // CraftBukkit - decompile error + } + + @Override +@@ -791,7 +843,7 @@ + this.canSpawnFarFromPlayer = spawnGroup == MobCategory.CREATURE || spawnGroup == MobCategory.MISC; + } + +- public static EntityType.Builder of(EntityType.EntityFactory factory, MobCategory spawnGroup) { ++ public static EntityType.Builder of(EntityType.EntityFactory factory, MobCategory spawnGroup) { // CraftBukkit - decompile error + return new EntityType.Builder<>(factory, spawnGroup); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch new file mode 100644 index 0000000000..2e0228c9d3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch @@ -0,0 +1,267 @@ +--- a/net/minecraft/world/entity/ExperienceOrb.java ++++ b/net/minecraft/world/entity/ExperienceOrb.java +@@ -24,6 +24,13 @@ + import net.minecraft.world.level.entity.EntityTypeTest; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.player.PlayerExpCooldownChangeEvent; ++// CraftBukkit end + + public class ExperienceOrb extends Entity { + +@@ -37,9 +44,63 @@ + public int value; + public int count; + private Player followingPlayer; ++ // Paper start ++ @javax.annotation.Nullable ++ public java.util.UUID sourceEntityId; ++ @javax.annotation.Nullable ++ public java.util.UUID triggerEntityId; ++ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; + ++ private void loadPaperNBT(CompoundTag tag) { ++ if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) { ++ return; ++ } ++ CompoundTag comp = tag.getCompound("Paper.ExpData"); ++ if (comp.hasUUID("source")) { ++ this.sourceEntityId = comp.getUUID("source"); ++ } ++ if (comp.hasUUID("trigger")) { ++ this.triggerEntityId = comp.getUUID("trigger"); ++ } ++ if (comp.contains("reason")) { ++ String reason = comp.getString("reason"); ++ try { ++ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason); ++ } catch (Exception e) { ++ this.level().getCraftServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason); ++ } ++ } ++ } ++ private void savePaperNBT(CompoundTag tag) { ++ CompoundTag comp = new CompoundTag(); ++ if (this.sourceEntityId != null) { ++ comp.putUUID("source", this.sourceEntityId); ++ } ++ if (this.triggerEntityId != null) { ++ comp.putUUID("trigger", triggerEntityId); ++ } ++ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) { ++ comp.putString("reason", this.spawnReason.name()); ++ } ++ tag.put("Paper.ExpData", comp); ++ } ++ ++ @io.papermc.paper.annotation.DoNotUse ++ @Deprecated + public ExperienceOrb(Level world, double x, double y, double z, int amount) { ++ this(world, x, y, z, amount, null, null); ++ } ++ ++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) { ++ this(world, x, y, z, amount, reason, triggerId, null); ++ } ++ ++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) { + this(EntityType.EXPERIENCE_ORB, world); ++ this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null; ++ this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null; ++ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // Paper end + this.setPos(x, y, z); + this.setYRot((float) (this.random.nextDouble() * 360.0D)); + this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D); +@@ -68,6 +129,7 @@ + @Override + public void tick() { + super.tick(); ++ Player prevTarget = this.followingPlayer;// CraftBukkit - store old target + this.xo = this.getX(); + this.yo = this.getY(); + this.zo = this.getZ(); +@@ -93,7 +155,22 @@ + this.followingPlayer = null; + } + +- if (this.followingPlayer != null) { ++ // CraftBukkit start ++ boolean cancelled = false; ++ if (this.followingPlayer != prevTarget) { ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this, this.followingPlayer, (this.followingPlayer != null) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.FORGOT_TARGET); ++ LivingEntity target = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ cancelled = event.isCancelled(); ++ ++ if (cancelled) { ++ this.followingPlayer = prevTarget; ++ } else { ++ this.followingPlayer = (target instanceof Player) ? (Player) target : null; ++ } ++ } ++ ++ if (this.followingPlayer != null && !cancelled) { ++ // CraftBukkit end + Vec3 vec3d = new Vec3(this.followingPlayer.getX() - this.getX(), this.followingPlayer.getY() + (double) this.followingPlayer.getEyeHeight() / 2.0D - this.getY(), this.followingPlayer.getZ() - this.getZ()); + double d0 = vec3d.lengthSqr(); + +@@ -121,7 +198,7 @@ + + ++this.age; + if (this.age >= 6000) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -150,18 +227,27 @@ + } + + public static void award(ServerLevel world, Vec3 pos, int amount) { ++ // Paper start - add reasons for orbs ++ award(world, pos, amount, null, null, null); ++ } ++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { ++ award(world, pos, amount, reason, triggerId, null); ++ } ++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { ++ // Paper end - add reasons for orbs + while (amount > 0) { + int j = ExperienceOrb.getExperienceValue(amount); + + amount -= j; + if (!ExperienceOrb.tryMergeToExisting(world, pos, j)) { +- world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j)); ++ world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j, reason, triggerId, sourceId)); // Paper - add reason + } + } + + } + + private static boolean tryMergeToExisting(ServerLevel world, Vec3 pos, int amount) { ++ // Paper - TODO some other event for this kind of merge + AABB axisalignedbb = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); + int j = world.getRandom().nextInt(40); + List list = world.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), axisalignedbb, (entityexperienceorb) -> { +@@ -188,9 +274,14 @@ + } + + private void merge(ExperienceOrb other) { ++ // Paper start - call orb merge event ++ if (!new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) this.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) other.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end - call orb merge event + this.count += other.count; + this.age = Math.min(this.age, other.age); +- other.discard(); ++ other.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause + } + + private void setUnderwaterMovement() { +@@ -215,7 +306,7 @@ + this.markHurt(); + this.health = (int) ((float) this.health - amount); + if (this.health <= 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } + + return true; +@@ -226,33 +317,35 @@ + public void addAdditionalSaveData(CompoundTag nbt) { + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); +- nbt.putShort("Value", (short) this.value); ++ nbt.putInt("Value", this.value); // Paper - save as Integer + nbt.putInt("Count", this.count); ++ this.savePaperNBT(nbt); // Paper + } + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + this.health = nbt.getShort("Health"); + this.age = nbt.getShort("Age"); +- this.value = nbt.getShort("Value"); ++ this.value = nbt.getInt("Value"); // Paper - load as Integer + this.count = Math.max(nbt.getInt("Count"), 1); ++ this.loadPaperNBT(nbt); // Paper + } + + @Override + public void playerTouch(Player player) { + if (player instanceof ServerPlayer entityplayer) { +- if (player.takeXpDelay == 0) { +- player.takeXpDelay = 2; ++ if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(entityplayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent ++ player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; + player.take(this, 1); + int i = this.repairPlayerItems(entityplayer, this.value); + + if (i > 0) { +- player.giveExperiencePoints(i); ++ player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object + } + + --this.count; + if (this.count == 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } + } + +@@ -266,12 +359,23 @@ + ItemStack itemstack = ((EnchantedItemInUse) optional.get()).itemStack(); + int j = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, amount); + int k = Math.min(j, itemstack.getDamageValue()); ++ // CraftBukkit start ++ // Paper start - mending event ++ final int consumedExperience = k > 0 ? k * amount / j : 0; ++ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k, consumedExperience); ++ // Paper end - mending event ++ k = event.getRepairAmount(); ++ if (event.isCancelled()) { ++ return amount; ++ } ++ // CraftBukkit end + + itemstack.setDamageValue(itemstack.getDamageValue() - k); + if (k > 0) { +- int l = amount - k * amount / j; ++ int l = amount - k * amount / j; // Paper - diff on change - expand PlayerMendEvents + + if (l > 0) { ++ // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account + return this.repairPlayerItems(player, l); + } + } +@@ -291,6 +395,24 @@ + } + + public static int getExperienceValue(int value) { ++ // CraftBukkit start ++ if (value > 162670129) return value - 100000; ++ if (value > 81335063) return 81335063; ++ if (value > 40667527) return 40667527; ++ if (value > 20333759) return 20333759; ++ if (value > 10166857) return 10166857; ++ if (value > 5083423) return 5083423; ++ if (value > 2541701) return 2541701; ++ if (value > 1270849) return 1270849; ++ if (value > 635413) return 635413; ++ if (value > 317701) return 317701; ++ if (value > 158849) return 158849; ++ if (value > 79423) return 79423; ++ if (value > 39709) return 39709; ++ if (value > 19853) return 19853; ++ if (value > 9923) return 9923; ++ if (value > 4957) return 4957; ++ // CraftBukkit end + return value >= 2477 ? 2477 : (value >= 1237 ? 1237 : (value >= 617 ? 617 : (value >= 307 ? 307 : (value >= 149 ? 149 : (value >= 73 ? 73 : (value >= 37 ? 37 : (value >= 17 ? 17 : (value >= 7 ? 7 : (value >= 3 ? 3 : 1))))))))); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch new file mode 100644 index 0000000000..4ee0117128 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/entity/Interaction.java ++++ b/net/minecraft/world/entity/Interaction.java +@@ -27,6 +27,12 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import net.minecraft.world.damagesource.DamageSource; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityDamageEvent; ++// CraftBukkit end ++ + public class Interaction extends Entity implements Attackable, Targeting { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -65,7 +71,7 @@ + this.setHeight(nbt.getFloat("height")); + } + +- DataResult dataresult; ++ DataResult> dataresult; // CraftBukkit - decompile error + Logger logger; + + if (nbt.contains("attack")) { +@@ -145,9 +151,16 @@ + @Override + public boolean skipAttackInteraction(Entity attacker) { + if (attacker instanceof Player entityhuman) { ++ // CraftBukkit start ++ DamageSource source = entityhuman.damageSources().playerAttack(entityhuman); ++ EntityDamageEvent event = CraftEventFactory.callNonLivingEntityDamageEvent(this, source, 1.0F, false); ++ if (event.isCancelled()) { ++ return true; ++ } ++ // CraftBukkit end + this.attack = new Interaction.PlayerAction(entityhuman.getUUID(), this.level().getGameTime()); + if (entityhuman instanceof ServerPlayer entityplayer) { +- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, 1.0F, false); ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order + } + + return !this.getResponse(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch new file mode 100644 index 0000000000..2f4eab4bc8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/entity/ItemBasedSteering.java ++++ b/net/minecraft/world/entity/ItemBasedSteering.java +@@ -53,6 +53,14 @@ + return (Integer) this.entityData.get(this.boostTimeAccessor); + } + ++ // CraftBukkit add setBoostTicks(int) ++ public void setBoostTicks(int ticks) { ++ this.boosting = true; ++ this.boostTime = 0; ++ this.entityData.set(this.boostTimeAccessor, ticks); ++ } ++ // CraftBukkit end ++ + public void addAdditionalSaveData(CompoundTag nbt) { + nbt.putBoolean("Saddle", this.hasSaddle()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch new file mode 100644 index 0000000000..aa36549d47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch @@ -0,0 +1,157 @@ +--- a/net/minecraft/world/entity/Leashable.java ++++ b/net/minecraft/world/entity/Leashable.java +@@ -15,6 +15,10 @@ + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityUnleashEvent; ++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++// CraftBukkit end + + public interface Leashable { + +@@ -45,7 +49,7 @@ + + default void setDelayedLeashHolderId(int unresolvedLeashHolderId) { + this.setLeashData(new Leashable.LeashData(unresolvedLeashHolderId)); +- Leashable.dropLeash((Entity) this, false, false); ++ Leashable.dropLeash((Entity & Leashable) this, false, false); // CraftBukkit - decompile error + } + + default void readLeashData(CompoundTag nbt) { +@@ -61,10 +65,16 @@ + @Nullable + private static Leashable.LeashData readLeashDataInternal(CompoundTag nbt) { + if (nbt.contains("leash", 10)) { +- return new Leashable.LeashData(Either.left(nbt.getCompound("leash").getUUID("UUID"))); ++ // Paper start ++ final CompoundTag leashTag = nbt.getCompound("leash"); ++ if (!leashTag.hasUUID("UUID")) { ++ return null; ++ } ++ return new Leashable.LeashData(Either.left(leashTag.getUUID("UUID"))); ++ // Paper end + } else { + if (nbt.contains("leash", 11)) { +- Either either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse((Object) null); ++ Either either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse(null); // CraftBukkit - decompile error + + if (either != null) { + return new Leashable.LeashData(either); +@@ -79,6 +89,11 @@ + if (leashData != null) { + Either either = leashData.delayedLeashInfo; + Entity entity = leashData.leashHolder; ++ // CraftBukkit start - SPIGOT-7487: Don't save (and possible drop) leash, when the holder was removed by a plugin ++ if (entity != null && entity.pluginRemoved) { ++ return; ++ } ++ // CraftBukkit end + + if (entity instanceof LeashFenceKnotEntity) { + LeashFenceKnotEntity entityleash = (LeashFenceKnotEntity) entity; +@@ -121,7 +136,9 @@ + } + + if (entity.tickCount > 100) { ++ entity.forceDrops = true; // CraftBukkit + entity.spawnAtLocation(worldserver, (ItemLike) Items.LEAD); ++ entity.forceDrops = false; // CraftBukkit + ((Leashable) entity).setLeashData((Leashable.LeashData) null); + } + } +@@ -130,11 +147,11 @@ + } + + default void dropLeash() { +- Leashable.dropLeash((Entity) this, true, true); ++ Leashable.dropLeash((Entity & Leashable) this, true, true); // CraftBukkit - decompile error + } + + default void removeLeash() { +- Leashable.dropLeash((Entity) this, true, false); ++ Leashable.dropLeash((Entity & Leashable) this, true, false); // CraftBukkit - decompile error + } + + default void onLeashRemoved() {} +@@ -151,7 +168,9 @@ + ServerLevel worldserver = (ServerLevel) world; + + if (dropItem) { ++ entity.forceDrops = true; // CraftBukkit + entity.spawnAtLocation(worldserver, (ItemLike) Items.LEAD); ++ entity.forceDrops = false; // CraftBukkit + } + + if (sendPacket) { +@@ -171,7 +190,11 @@ + + if (leashable_a != null && leashable_a.leashHolder != null) { + if (!entity.isAlive() || !leashable_a.leashHolder.isAlive()) { +- if (world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved); ++ event.callEvent(); ++ if (event.isDropLeash()) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin ++ // Paper end - Expand EntityUnleashEvent + ((Leashable) entity).dropLeash(); + } else { + ((Leashable) entity).removeLeash(); +@@ -187,7 +210,7 @@ + return; + } + +- if ((double) f > 10.0D) { ++ if ((double) f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance + ((Leashable) entity).leashTooFarBehaviour(); + } else if ((double) f > 6.0D) { + ((Leashable) entity).elasticRangeLeashBehaviour(entity1, f); +@@ -205,13 +228,27 @@ + } + + default void leashTooFarBehaviour() { +- this.dropLeash(); ++ // CraftBukkit start ++ boolean dropLeash = true; // Paper ++ if (this instanceof Entity entity) { ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return; ++ dropLeash = event.isDropLeash(); ++ } ++ // CraftBukkit end ++ if (dropLeash) { ++ this.dropLeash(); ++ } else { ++ this.removeLeash(); ++ } ++ // Paper end - Expand EntityUnleashEvent + } + + default void closeRangeLeashBehaviour(Entity entity) {} + + default void elasticRangeLeashBehaviour(Entity leashHolder, float distance) { +- Leashable.legacyElasticRangeLeashBehaviour((Entity) this, leashHolder, distance); ++ Leashable.legacyElasticRangeLeashBehaviour((Entity & Leashable) this, leashHolder, distance); // CraftBukkit - decompile error + } + + private static void legacyElasticRangeLeashBehaviour(E entity, Entity leashHolder, float distance) { +@@ -223,7 +260,7 @@ + } + + default void setLeashedTo(Entity leashHolder, boolean sendPacket) { +- this.setLeashedTo((Entity) this, leashHolder, sendPacket); ++ Leashable.setLeashedTo((Entity & Leashable) this, leashHolder, sendPacket); // CraftBukkit - decompile error + } + + private static void setLeashedTo(E entity, Entity leashHolder, boolean sendPacket) { +@@ -254,7 +291,7 @@ + + @Nullable + default Entity getLeashHolder() { +- return Leashable.getLeashHolder((Entity) this); ++ return Leashable.getLeashHolder((Entity & Leashable) this); // CraftBukkit - decompile error + } + + @Nullable diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch new file mode 100644 index 0000000000..1dc2b6bf90 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch @@ -0,0 +1,160 @@ +--- a/net/minecraft/world/entity/LightningBolt.java ++++ b/net/minecraft/world/entity/LightningBolt.java +@@ -30,6 +30,10 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class LightningBolt extends Entity { + +@@ -44,6 +48,7 @@ + private ServerPlayer cause; + private final Set hitEntities = Sets.newHashSet(); + private int blocksSetOnFire; ++ public boolean isEffect; // Paper - Properly handle lightning effects api + + public LightningBolt(EntityType type, Level world) { + super(type, world); +@@ -82,7 +87,7 @@ + @Override + public void tick() { + super.tick(); +- if (this.life == 2) { ++ if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api + if (this.level().isClientSide()) { + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false); + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false); +@@ -94,7 +99,7 @@ + } + + this.powerLightningRod(); +- LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition()); ++ LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent + this.gameEvent(GameEvent.LIGHTNING_STRIKE); + } + } +@@ -120,7 +125,7 @@ + } + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (this.life < -this.random.nextInt(10)) { + --this.flashes; + this.life = 1; +@@ -129,7 +134,7 @@ + } + } + +- if (this.life >= 0) { ++ if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api + if (!(this.level() instanceof ServerLevel)) { + this.level().setSkyFlashTime(2); + } else if (!this.visualOnly) { +@@ -158,7 +163,7 @@ + } + + private void spawnFire(int spreadAttempts) { +- if (!this.visualOnly) { ++ if (!this.visualOnly && !this.isEffect) { // Paper - Properly handle lightning effects api + Level world = this.level(); + + if (world instanceof ServerLevel) { +@@ -169,8 +174,12 @@ + BlockState iblockdata = BaseFireBlock.getState(this.level(), blockposition); + + if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) { +- this.level().setBlockAndUpdate(blockposition, iblockdata); +- ++this.blocksSetOnFire; ++ // CraftBukkit start - add "!visualOnly" ++ if (!this.visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { ++ this.level().setBlockAndUpdate(blockposition, iblockdata); ++ ++this.blocksSetOnFire; ++ } ++ // CraftBukkit end + } + + for (int j = 0; j < spreadAttempts; ++j) { +@@ -178,8 +187,12 @@ + + iblockdata = BaseFireBlock.getState(this.level(), blockposition1); + if (this.level().getBlockState(blockposition1).isAir() && iblockdata.canSurvive(this.level(), blockposition1)) { +- this.level().setBlockAndUpdate(blockposition1, iblockdata); +- ++this.blocksSetOnFire; ++ // CraftBukkit start - add "!visualOnly" ++ if (!this.visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition1, this).isCancelled()) { ++ this.level().setBlockAndUpdate(blockposition1, iblockdata); ++ ++this.blocksSetOnFire; ++ } ++ // CraftBukkit end + } + } + +@@ -190,7 +203,7 @@ + + } + +- private static void clearCopperOnLightningStrike(Level world, BlockPos pos) { ++ private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent + BlockState iblockdata = world.getBlockState(pos); + BlockPos blockposition1; + BlockState iblockdata1; +@@ -204,24 +217,29 @@ + } + + if (iblockdata1.getBlock() instanceof WeatheringCopper) { +- world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1))); ++ // Paper start - Call EntityChangeBlockEvent ++ BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1)); ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) { ++ world.setBlockAndUpdate(blockposition1, newBlock); ++ } ++ // Paper end - Call EntityChangeBlockEvent + BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); + int i = world.random.nextInt(3) + 3; + + for (int j = 0; j < i; ++j) { + int k = world.random.nextInt(8) + 1; + +- LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k); ++ LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + } + + } + } + +- private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) { ++ private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + mutablePos.set(pos); + + for (int j = 0; j < count; ++j) { +- Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos); ++ Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + + if (optional.isEmpty()) { + break; +@@ -232,7 +250,7 @@ + + } + +- private static Optional randomStepCleaningCopper(Level world, BlockPos pos) { ++ private static Optional randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator(); + + BlockPos blockposition1; +@@ -247,8 +265,10 @@ + iblockdata = world.getBlockState(blockposition1); + } while (!(iblockdata.getBlock() instanceof WeatheringCopper)); + ++ BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error + WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> { +- world.setBlockAndUpdate(blockposition1, iblockdata1); ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent ++ world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error + }); + world.levelEvent(3002, blockposition1, -1); + return Optional.of(blockposition1); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch new file mode 100644 index 0000000000..c1e602ad26 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -0,0 +1,2034 @@ +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -42,6 +42,8 @@ + import net.minecraft.core.particles.ParticleOptions; + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.FloatTag; ++import net.minecraft.nbt.IntTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtOps; + import net.minecraft.nbt.Tag; +@@ -94,7 +96,6 @@ + import net.minecraft.world.entity.animal.Wolf; + import net.minecraft.world.entity.boss.wither.WitherBoss; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.Projectile; + import net.minecraft.world.item.AxeItem; +@@ -135,6 +136,30 @@ + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.Scoreboard; + import org.slf4j.Logger; ++ ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.LinkedList; ++import java.util.UUID; ++import net.minecraft.world.item.component.Consumable; ++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.Player; ++import org.bukkit.event.entity.ArrowBodyCountChangeEvent; ++import org.bukkit.event.entity.EntityDamageEvent; ++import org.bukkit.event.entity.EntityDamageEvent.DamageModifier; ++import org.bukkit.event.entity.EntityKnockbackEvent; ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityResurrectEvent; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.event.player.PlayerItemConsumeEvent; ++// CraftBukkit end + + public abstract class LivingEntity extends Entity implements Attackable { + +@@ -174,7 +199,7 @@ + public static final float DEFAULT_BABY_SCALE = 0.5F; + public static final String ATTRIBUTES_FIELD = "attributes"; + public static final Predicate PLAYER_NOT_WEARING_DISGUISE_ITEM = (entityliving) -> { +- if (entityliving instanceof Player entityhuman) { ++ if (entityliving instanceof net.minecraft.world.entity.player.Player entityhuman) { + ItemStack itemstack = entityhuman.getItemBySlot(EquipmentSlot.HEAD); + + return !itemstack.is(ItemTags.GAZE_DISGUISE_EQUIPMENT); +@@ -210,7 +235,7 @@ + public float yHeadRotO; + public final ElytraAnimationState elytraAnimationState; + @Nullable +- public Player lastHurtByPlayer; ++ public net.minecraft.world.entity.player.Player lastHurtByPlayer; + public int lastHurtByPlayerTime; + protected boolean dead; + protected int noActionTime; +@@ -260,7 +285,30 @@ + protected boolean skipDropExperience; + private final EnumMap>> activeLocationDependentEnchantments; + protected float appliedScale; ++ // CraftBukkit start ++ public int expToDrop; ++ public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior ++ public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; ++ public boolean collides = true; ++ public Set collidableExemptions = new HashSet<>(); ++ public boolean bukkitPickUpLoot; ++ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper ++ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + ++ @Override ++ public float getBukkitYaw() { ++ return this.getYHeadRot(); ++ } ++ // CraftBukkit end ++ // Spigot start ++ public void inactiveTick() ++ { ++ super.inactiveTick(); ++ ++this.noActionTime; // Above all the floats ++ } ++ // Spigot end ++ + protected LivingEntity(EntityType type, Level world) { + super(type, world); + this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY); +@@ -276,7 +324,9 @@ + this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class); + this.appliedScale = 1.0F; + this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); +- this.setHealth(this.getMaxHealth()); ++ this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit ++ // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor ++ this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue()); + this.blocksBuilding = true; + this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D); + this.reapplyPosition(); +@@ -356,7 +406,13 @@ + double d8 = Math.min((double) (0.2F + f / 15.0F), 2.5D); + int i = (int) (150.0D * d8); + +- worldserver.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D); ++ // CraftBukkit start - visiblity api ++ if (this instanceof ServerPlayer) { ++ worldserver.sendParticlesSource((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, state), false, false, d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D); ++ } else { ++ worldserver.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D); ++ } ++ // CraftBukkit end + } + } + } +@@ -402,7 +458,7 @@ + } + + if (this.isAlive()) { +- boolean flag = this instanceof Player; ++ boolean flag = this instanceof net.minecraft.world.entity.player.Player; + Level world1 = this.level(); + ServerLevel worldserver1; + double d0; +@@ -424,7 +480,7 @@ + } + + if (this.isEyeInFluid(FluidTags.WATER) && !this.level().getBlockState(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) { +- boolean flag1 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!flag || !((Player) this).getAbilities().invulnerable); ++ boolean flag1 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!flag || !((net.minecraft.world.entity.player.Player) this).getAbilities().invulnerable); + + if (flag1) { + this.setAirSupply(this.decreaseAirSupply(this.getAirSupply())); +@@ -573,7 +629,7 @@ + ++this.deathTime; + if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { + this.level().broadcastEntityEvent(this, (byte) 60); +- this.remove(Entity.RemovalReason.KILLED); ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -629,7 +685,7 @@ + return this.lastHurtByMobTimestamp; + } + +- public void setLastHurtByPlayer(@Nullable Player attacking) { ++ public void setLastHurtByPlayer(@Nullable net.minecraft.world.entity.player.Player attacking) { + this.lastHurtByPlayer = attacking; + this.lastHurtByPlayerTime = this.tickCount; + } +@@ -667,7 +723,7 @@ + } + + public boolean shouldDiscardFriction() { +- return this.discardFriction; ++ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API + } + + public void setDiscardFriction(boolean noDrag) { +@@ -679,17 +735,23 @@ + } + + public void onEquipItem(EquipmentSlot slot, ItemStack oldStack, ItemStack newStack) { +- if (!this.level().isClientSide() && !this.isSpectator()) { +- boolean flag = newStack.isEmpty() && oldStack.isEmpty(); +- +- if (!flag && !ItemStack.isSameItemSameComponents(oldStack, newStack) && !this.firstTick) { +- Equippable equippable = (Equippable) newStack.get(DataComponents.EQUIPPABLE); ++ // CraftBukkit start ++ this.onEquipItem(slot, oldStack, newStack, false); ++ } + +- if (!this.isSilent() && equippable != null && slot == equippable.slot()) { +- this.level().playSeededSound((Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()); ++ public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) { ++ // CraftBukkit end ++ if (!this.level().isClientSide() && !this.isSpectator()) { ++ boolean flag = itemstack1.isEmpty() && itemstack.isEmpty(); ++ ++ if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) { ++ Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE); ++ ++ if (!this.isSilent() && equippable != null && enumitemslot == equippable.slot() && !silent) { // CraftBukkit ++ this.level().playSeededSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()); + } + +- if (this.doesEmitEquipEvent(slot)) { ++ if (this.doesEmitEquipEvent(enumitemslot)) { + this.gameEvent(equippable != null ? GameEvent.EQUIP : GameEvent.UNEQUIP); + } + +@@ -699,17 +761,24 @@ + + @Override + public void remove(Entity.RemovalReason reason) { +- if (reason == Entity.RemovalReason.KILLED || reason == Entity.RemovalReason.DISCARDED) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end ++ if (entity_removalreason == Entity.RemovalReason.KILLED || entity_removalreason == Entity.RemovalReason.DISCARDED) { + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.triggerOnDeathMobEffects(worldserver, reason); ++ this.triggerOnDeathMobEffects(worldserver, entity_removalreason); + } + } + +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit + this.brain.clearMemories(); + } + +@@ -722,11 +791,17 @@ + mobeffect.onMobRemoved(world, this, reason); + } + ++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); // CraftBukkit + this.activeEffects.clear(); + } + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putFloat("Health", this.getHealth()); + nbt.putShort("HurtTime", (short) this.hurtTime); + nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); +@@ -763,7 +838,23 @@ + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { +- this.internalSetAbsorptionAmount(nbt.getFloat("AbsorptionAmount")); ++ // Paper start - Check for NaN ++ float absorptionAmount = nbt.getFloat("AbsorptionAmount"); ++ if (Float.isNaN(absorptionAmount)) { ++ absorptionAmount = 0; ++ } ++ this.internalSetAbsorptionAmount(absorptionAmount); ++ // Paper end - Check for NaN ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) { + this.getAttributes().load(nbt.getList("attributes", 10)); + } +@@ -781,6 +872,17 @@ + } + } + ++ // CraftBukkit start ++ if (nbt.contains("Bukkit.MaxHealth")) { ++ Tag nbtbase = nbt.get("Bukkit.MaxHealth"); ++ if (nbtbase.getId() == 5) { ++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) nbtbase).getAsDouble()); ++ } else if (nbtbase.getId() == 3) { ++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) nbtbase).getAsDouble()); ++ } ++ } ++ // CraftBukkit end ++ + if (nbt.contains("Health", 99)) { + this.setHealth(nbt.getFloat("Health")); + } +@@ -792,6 +894,7 @@ + String s = nbt.getString("Team"); + Scoreboard scoreboard = this.level().getScoreboard(); + PlayerTeam scoreboardteam = scoreboard.getPlayerTeam(s); ++ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper - Perf: Disable Scoreboards for non players by default + boolean flag = scoreboardteam != null && scoreboard.addPlayerToTeam(this.getStringUUID(), scoreboardteam); + + if (!flag) { +@@ -806,11 +909,13 @@ + if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) { + BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ")); + ++ if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + this.setSleepingPos(blockposition); + this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING); + if (!this.firstTick) { + this.setPosToBed(blockposition); + } ++ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + } + + if (nbt.contains("Brain", 10)) { +@@ -819,9 +924,32 @@ + + } + ++ // CraftBukkit start ++ private boolean isTickingEffects = false; ++ private List effectsToProcess = Lists.newArrayList(); ++ ++ private static class ProcessableEffect { ++ ++ private Holder type; ++ private MobEffectInstance effect; ++ private final EntityPotionEffectEvent.Cause cause; ++ ++ private ProcessableEffect(MobEffectInstance effect, EntityPotionEffectEvent.Cause cause) { ++ this.effect = effect; ++ this.cause = cause; ++ } ++ ++ private ProcessableEffect(Holder type, EntityPotionEffectEvent.Cause cause) { ++ this.type = type; ++ this.cause = cause; ++ } ++ } ++ // CraftBukkit end ++ + protected void tickEffects() { + Iterator> iterator = this.activeEffects.keySet().iterator(); + ++ this.isTickingEffects = true; // CraftBukkit + try { + while (iterator.hasNext()) { + Holder holder = (Holder) iterator.next(); +@@ -831,6 +959,12 @@ + this.onEffectUpdated(mobeffect, true, (Entity) null); + })) { + if (!this.level().isClientSide) { ++ // CraftBukkit start ++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION); ++ if (event.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + iterator.remove(); + this.onEffectsRemoved(List.of(mobeffect)); + } +@@ -841,6 +975,17 @@ + } catch (ConcurrentModificationException concurrentmodificationexception) { + ; + } ++ // CraftBukkit start ++ this.isTickingEffects = false; ++ for (ProcessableEffect e : this.effectsToProcess) { ++ if (e.effect != null) { ++ this.addEffect(e.effect, e.cause); ++ } else { ++ this.removeEffect(e.type, e.cause); ++ } ++ } ++ this.effectsToProcess.clear(); ++ // CraftBukkit end + + if (this.effectsDirty) { + if (!this.level().isClientSide) { +@@ -921,7 +1066,7 @@ + } + + public boolean canAttack(LivingEntity target) { +- return target instanceof Player && this.level().getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy(); ++ return target instanceof net.minecraft.world.entity.player.Player && this.level().getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy(); + } + + public boolean canBeSeenAsEnemy() { +@@ -952,17 +1097,36 @@ + this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, List.of()); + } + ++ // CraftBukkit start + public boolean removeAllEffects() { ++ return this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN); ++ } ++ ++ public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) { ++ // CraftBukkit end + if (this.level().isClientSide) { + return false; + } else if (this.activeEffects.isEmpty()) { + return false; + } else { +- Map, MobEffectInstance> map = Maps.newHashMap(this.activeEffects); ++ // CraftBukkit start ++ List toRemove = new LinkedList<>(); ++ Iterator iterator = this.activeEffects.values().iterator(); + +- this.activeEffects.clear(); +- this.onEffectsRemoved(map.values()); +- return true; ++ while (iterator.hasNext()) { ++ MobEffectInstance effect = iterator.next(); ++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED); ++ if (event.isCancelled()) { ++ continue; ++ } ++ ++ iterator.remove(); ++ toRemove.add(effect); ++ } ++ ++ this.onEffectsRemoved(toRemove); ++ return !toRemove.isEmpty(); ++ // CraftBukkit end + } + } + +@@ -987,24 +1151,63 @@ + return this.addEffect(effect, (Entity) null); + } + ++ // CraftBukkit start ++ public boolean addEffect(MobEffectInstance mobeffect, EntityPotionEffectEvent.Cause cause) { ++ return this.addEffect(mobeffect, (Entity) null, cause); ++ } ++ + public boolean addEffect(MobEffectInstance effect, @Nullable Entity source) { +- if (!this.canBeAffected(effect)) { ++ return this.addEffect(effect, source, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN); ++ } ++ ++ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { ++ // Paper start - Don't fire sync event during generation ++ return this.addEffect(mobeffect, entity, cause, true); ++ } ++ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { ++ // Paper end - Don't fire sync event during generation ++ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API ++ if (this.isTickingEffects) { ++ this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); ++ return true; ++ } ++ // CraftBukkit end ++ ++ if (!this.canBeAffected(mobeffect)) { + return false; + } else { +- MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(effect.getEffect()); ++ MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(mobeffect.getEffect()); + boolean flag = false; + ++ // CraftBukkit start ++ boolean override = false; ++ if (mobeffect1 != null) { ++ override = new MobEffectInstance(mobeffect1).update(mobeffect); ++ } ++ ++ if (fireEvent) { // Paper - Don't fire sync event during generation ++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override); ++ override = event.isOverride(); // Paper - Don't fire sync event during generation ++ if (event.isCancelled()) { ++ return false; ++ } ++ } // Paper - Don't fire sync event during generation ++ // CraftBukkit end ++ + if (mobeffect1 == null) { +- this.activeEffects.put(effect.getEffect(), effect); +- this.onEffectAdded(effect, source); ++ this.activeEffects.put(mobeffect.getEffect(), mobeffect); ++ this.onEffectAdded(mobeffect, entity); + flag = true; +- effect.onEffectAdded(this); +- } else if (mobeffect1.update(effect)) { +- this.onEffectUpdated(mobeffect1, true, source); ++ mobeffect.onEffectAdded(this); ++ // CraftBukkit start ++ } else if (override) { // Paper - Don't fire sync event during generation ++ mobeffect1.update(mobeffect); ++ this.onEffectUpdated(mobeffect1, true, entity); ++ // CraftBukkit end + flag = true; + } + +- effect.onEffectStarted(this); ++ mobeffect.onEffectStarted(this); + return flag; + } + } +@@ -1031,14 +1234,40 @@ + return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM); + } + ++ // CraftBukkit start + @Nullable + public MobEffectInstance removeEffectNoUpdate(Holder effect) { +- return (MobEffectInstance) this.activeEffects.remove(effect); ++ return this.removeEffectNoUpdate(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN); + } + ++ @Nullable ++ public MobEffectInstance removeEffectNoUpdate(Holder holder, EntityPotionEffectEvent.Cause cause) { ++ if (this.isTickingEffects) { ++ this.effectsToProcess.add(new ProcessableEffect(holder, cause)); ++ return null; ++ } ++ ++ MobEffectInstance effect = this.activeEffects.get(holder); ++ if (effect == null) { ++ return null; ++ } ++ ++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause); ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ return (MobEffectInstance) this.activeEffects.remove(holder); ++ } ++ + public boolean removeEffect(Holder effect) { +- MobEffectInstance mobeffect = this.removeEffectNoUpdate(effect); ++ return this.removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN); ++ } + ++ public boolean removeEffect(Holder holder, EntityPotionEffectEvent.Cause cause) { ++ MobEffectInstance mobeffect = this.removeEffectNoUpdate(holder, cause); ++ // CraftBukkit end ++ + if (mobeffect != null) { + this.onEffectsRemoved(List.of(mobeffect)); + return true; +@@ -1142,20 +1371,65 @@ + + } + ++ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained + public void heal(float amount) { ++ this.heal(amount, EntityRegainHealthEvent.RegainReason.CUSTOM); ++ } ++ ++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) { ++ // Paper start - Forward ++ heal(f, regainReason, false); ++ } ++ ++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) { ++ // Paper end + float f1 = this.getHealth(); + + if (f1 > 0.0F) { +- this.setHealth(f1 + amount); ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper ++ // Suppress during worldgen ++ if (this.valid) { ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ } ++ ++ if (!event.isCancelled()) { ++ this.setHealth((float) (this.getHealth() + event.getAmount())); ++ } ++ // CraftBukkit end + } + + } + + public float getHealth() { ++ // CraftBukkit start - Use unscaled health ++ if (this instanceof ServerPlayer) { ++ return (float) ((ServerPlayer) this).getBukkitEntity().getHealth(); ++ } ++ // CraftBukkit end + return (Float) this.entityData.get(LivingEntity.DATA_HEALTH_ID); + } + + public void setHealth(float health) { ++ // Paper start - Check for NaN ++ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) { ++ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set"); ++ } } // Paper end - Check for NaN ++ // CraftBukkit start - Handle scaled health ++ if (this instanceof ServerPlayer) { ++ org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) 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(false); ++ return; ++ } ++ // CraftBukkit end + this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth())); + } + +@@ -1167,7 +1441,7 @@ + public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { + if (this.isInvulnerableTo(world, source)) { + return false; +- } else if (this.isDeadOrDying()) { ++ } else if (this.isRemoved() || this.dead || this.getHealth() <= 0.0F) { // CraftBukkit - Don't allow entities that got set to dead/killed elsewhere to get damaged and die + return false; + } else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) { + return false; +@@ -1181,11 +1455,12 @@ + amount = 0.0F; + } + +- float f1 = amount; +- boolean flag = false; ++ float f1 = amount; final float originalAmount = f1; // Paper - revert to vanilla #hurt - OBFHELPER ++ boolean flag = amount > 0.0F && this.isDamageSourceBlocked(source); // Copied from below + float f2 = 0.0F; + +- if (amount > 0.0F && this.isDamageSourceBlocked(source)) { ++ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage ++ if (false && amount > 0.0F && this.isDamageSourceBlocked(source)) { + this.hurtCurrentlyUsedShield(amount); + f2 = amount; + amount = 0.0F; +@@ -1202,15 +1477,21 @@ + flag = true; + } + +- if (source.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) { ++ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f ++ if (false && source.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) { + amount *= 5.0F; + } + +- if (source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { ++ // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage ++ if (false && source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { + this.hurtHelmet(source, amount); + amount *= 0.75F; + } + ++ // CraftBukkit start ++ EntityDamageEvent event; // Paper - move this into the actual invuln check.... ++ // CraftBukkit end ++ + this.walkAnimation.setSpeed(1.5F); + if (Float.isNaN(amount) || Float.isInfinite(amount)) { + amount = Float.MAX_VALUE; +@@ -1218,18 +1499,38 @@ + + boolean flag1 = true; + +- if ((float) this.invulnerableTime > 10.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) { ++ if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) { // CraftBukkit - restore use of maxNoDamageTicks + if (amount <= this.lastHurt) { + return false; + } + +- this.actuallyHurt(world, source, amount - this.lastHurt); ++ // Paper start - only call damage event when actuallyHurt will be called - move call logic down ++ event = this.handleEntityDamage(source, amount, this.lastHurt); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction ++ amount = computeAmountFromEntityDamageEvent(event); ++ // Paper end - only call damage event when actuallyHurt will be called - move call logic down ++ ++ // CraftBukkit start ++ if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) { // Paper - fix invulnerability reduction in EntityDamageEvent - no longer subtract lastHurt, that is part of the damage event calc now ++ return false; ++ } ++ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event. ++ // CraftBukkit end + this.lastHurt = amount; + flag1 = false; + } else { ++ // Paper start - only call damage event when actuallyHurt will be called - move call logic down ++ event = this.handleEntityDamage(source, amount, 0); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction (none in this branch) ++ amount = computeAmountFromEntityDamageEvent(event); ++ // Paper end - only call damage event when actuallyHurt will be called - move call logic down ++ // CraftBukkit start ++ if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) { ++ return false; ++ } ++ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event. + this.lastHurt = amount; +- this.invulnerableTime = 20; +- this.actuallyHurt(world, source, amount); ++ this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks ++ // this.actuallyHurt(worldserver, damagesource, f); ++ // CraftBukkit end + this.hurtDuration = 10; + this.hurtTime = this.hurtDuration; + } +@@ -1243,7 +1544,7 @@ + world.broadcastDamageEvent(this, source); + } + +- if (!source.is(DamageTypeTags.NO_IMPACT) && (!flag || amount > 0.0F)) { ++ if (!source.is(DamageTypeTags.NO_IMPACT) && !flag) { // CraftBukkit - Prevent marking hurt if the damage is blocked + this.markHurt(); + } + +@@ -1263,7 +1564,7 @@ + d1 = source.getSourcePosition().z() - this.getZ(); + } + +- this.knockback(0.4000000059604645D, d0, d1); ++ this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events + if (!flag) { + this.indicateDamage(d0, d1); + } +@@ -1272,17 +1573,18 @@ + + if (this.isDeadOrDying()) { + if (!this.checkTotemDeathProtection(source)) { +- if (flag1) { +- this.makeSound(this.getDeathSound()); +- } ++ // Paper start - moved into CraftEventFactory event caller for cancellable death event ++ this.silentDeath = !flag1; // mark entity as dying silently ++ // Paper end + + this.die(source); ++ this.silentDeath = false; // Paper - cancellable death event - reset to default + } + } else if (flag1) { + this.playHurtSound(source); + } + +- boolean flag2 = !flag || amount > 0.0F; ++ boolean flag2 = !flag; // CraftBukkit - Ensure to return false if damage is blocked + + if (flag2) { + this.lastDamageSource = source; +@@ -1329,10 +1631,10 @@ + } + + @Nullable +- protected Player resolvePlayerResponsibleForDamage(DamageSource damageSource) { ++ protected net.minecraft.world.entity.player.Player resolvePlayerResponsibleForDamage(DamageSource damageSource) { + Entity entity = damageSource.getEntity(); + +- if (entity instanceof Player entityhuman) { ++ if (entity instanceof net.minecraft.world.entity.player.Player entityhuman) { + this.lastHurtByPlayerTime = 100; + this.lastHurtByPlayer = entityhuman; + return entityhuman; +@@ -1342,8 +1644,8 @@ + this.lastHurtByPlayerTime = 100; + LivingEntity entityliving = entitywolf.getOwner(); + +- if (entityliving instanceof Player) { +- Player entityhuman1 = (Player) entityliving; ++ if (entityliving instanceof net.minecraft.world.entity.player.Player) { ++ net.minecraft.world.entity.player.Player entityhuman1 = (net.minecraft.world.entity.player.Player) entityliving; + + this.lastHurtByPlayer = entityhuman1; + } else { +@@ -1358,12 +1660,24 @@ + } + } + ++ // Paper start - only call damage event when actuallyHurt will be called - move out amount computation logic ++ private float computeAmountFromEntityDamageEvent(final EntityDamageEvent event) { ++ // Taken from hurt()'s craftbukkit diff. ++ float amount = 0; ++ amount += (float) event.getDamage(DamageModifier.BASE); ++ amount += (float) event.getDamage(DamageModifier.BLOCKING); ++ amount += (float) event.getDamage(DamageModifier.FREEZING); ++ amount += (float) event.getDamage(DamageModifier.HARD_HAT); ++ return amount; ++ } ++ // Paper end - only call damage event when actuallyHurt will be called - move out amount computation logic ++ + protected void blockUsingShield(LivingEntity attacker) { + attacker.blockedByShield(this); + } + + protected void blockedByShield(LivingEntity target) { +- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ()); ++ target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events + } + + private boolean checkTotemDeathProtection(DamageSource source) { +@@ -1375,20 +1689,39 @@ + InteractionHand[] aenumhand = InteractionHand.values(); + int i = aenumhand.length; + ++ // CraftBukkit start ++ InteractionHand hand = null; ++ ItemStack itemstack1 = ItemStack.EMPTY; + for (int j = 0; j < i; ++j) { + InteractionHand enumhand = aenumhand[j]; +- ItemStack itemstack1 = this.getItemInHand(enumhand); ++ itemstack1 = this.getItemInHand(enumhand); + + deathprotection = (DeathProtection) itemstack1.get(DataComponents.DEATH_PROTECTION); + if (deathprotection != null) { ++ hand = enumhand; // CraftBukkit + itemstack = itemstack1.copy(); +- itemstack1.shrink(1); ++ // itemstack1.subtract(1); // CraftBukkit + break; + } + } + +- if (itemstack != null) { +- if (this instanceof ServerPlayer) { ++ org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null; ++ EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot); ++ event.setCancelled(itemstack == null); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found ++ itemstack1.shrink(1); ++ } ++ // Paper start - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled ++ // restore the previous behavior in that case by defaulting to vanillas totem of undying efect ++ if (deathprotection == null) { ++ deathprotection = DeathProtection.TOTEM_OF_UNDYING; ++ } ++ // Paper end - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled ++ if (itemstack != null && this instanceof ServerPlayer) { ++ // CraftBukkit end + ServerPlayer entityplayer = (ServerPlayer) this; + + entityplayer.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); +@@ -1468,6 +1801,7 @@ + Entity entity = damageSource.getEntity(); + LivingEntity entityliving = this.getKillCredit(); + ++ /* // Paper - move down to make death event cancellable - this is the awardKillScore below + if (entityliving != null) { + entityliving.awardKillScore(this, damageSource); + } +@@ -1477,26 +1811,61 @@ + } + + if (!this.level().isClientSide && this.hasCustomName()) { +- LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); ++ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot + } ++ */ // Paper - move down to make death event cancellable - this is the awardKillScore below + + this.dead = true; +- this.getCombatTracker().recheckStatus(); ++ // Paper - moved into if below + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; ++ // Paper - move below into if for onKill + +- if (entity == null || entity.killedEntity(worldserver, this)) { ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ //if (entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent ++ // entityliving.awardKillScore(this, damageSource); ++ //} ++ // Paper start - clear equipment if event is not cancelled ++ if (this instanceof Mob) { ++ for (EquipmentSlot slot : this.clearedEquipmentSlots) { ++ this.setItemSlot(slot, ItemStack.EMPTY); ++ } ++ this.clearedEquipmentSlots.clear(); ++ } ++ // Paper end ++ ++ if (this.isSleeping()) { ++ this.stopSleeping(); ++ } ++ ++ if (!this.level().isClientSide && this.hasCustomName()) { ++ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot ++ } ++ ++ this.getCombatTracker().recheckStatus(); ++ if (entity != null) { ++ entity.killedEntity((ServerLevel) this.level(), this); ++ } + this.gameEvent(GameEvent.ENTITY_DIE); +- this.dropAllDeathLoot(worldserver, damageSource); ++ } else { ++ this.dead = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); ++ } ++ // Paper end + this.createWitherRose(entityliving); + } + ++ // Paper start ++ if (this.dead) { // Paper + this.level().broadcastEntityEvent(this, (byte) 3); +- } + + this.setPose(Pose.DYING); ++ } ++ // Paper end + } + } + +@@ -1506,20 +1875,28 @@ + if (world instanceof ServerLevel worldserver) { + boolean flag = false; + +- if (adversary instanceof WitherBoss) { ++ if (this.dead && adversary instanceof WitherBoss) { // Paper + if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + BlockPos blockposition = this.blockPosition(); + BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); + + if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) { +- this.level().setBlock(blockposition, iblockdata, 3); +- flag = true; ++ // CraftBukkit start - call EntityBlockFormEvent for Wither Rose ++ flag = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, 3, this); ++ // CraftBukkit end + } + } + + if (!flag) { + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE)); + ++ // CraftBukkit start ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ CraftEventFactory.callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.level().addFreshEntity(entityitem); + } + } +@@ -1527,27 +1904,60 @@ + } + } + +- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ // Paper start ++ protected boolean clearEquipmentSlots = true; ++ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ // Paper end + boolean flag = this.lastHurtByPlayerTime > 0; + ++ this.dropEquipment(world); // CraftBukkit - from below + if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + this.dropFromLootTable(world, damageSource, flag); ++ // Paper start ++ final boolean prev = this.clearEquipmentSlots; ++ this.clearEquipmentSlots = false; ++ this.clearedEquipmentSlots.clear(); ++ // Paper end + this.dropCustomDeathLoot(world, damageSource, flag); ++ this.clearEquipmentSlots = prev; // Paper + } +- +- this.dropEquipment(world); ++ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> { ++ final LivingEntity entityliving = this.getKillCredit(); ++ if (entityliving != null) { ++ entityliving.awardKillScore(this, damageSource); ++ } ++ }); // Paper end ++ this.postDeathDropItems(deathEvent); // Paper ++ this.drops = new ArrayList<>(); ++ // CraftBukkit end ++ ++ // this.dropEquipment(worldserver);// CraftBukkit - moved up + this.dropExperience(world, damageSource.getEntity()); ++ return deathEvent; // Paper + } + + protected void dropEquipment(ServerLevel world) {} ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled + +- protected void dropExperience(ServerLevel world, @Nullable Entity attacker) { +- if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) { +- ExperienceOrb.award(world, this.position(), this.getExperienceReward(world, attacker)); ++ public int getExpReward(ServerLevel worldserver, @Nullable Entity entity) { // CraftBukkit ++ if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) { ++ return this.getExperienceReward(worldserver, entity); // CraftBukkit } + } + ++ return 0; // CraftBukkit + } + ++ protected void dropExperience(ServerLevel world, @Nullable Entity attacker) { ++ // CraftBukkit start - Update getExpReward() above if the removed if() changes! ++ if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time ++ ExperienceOrb.award(world, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper ++ this.expToDrop = 0; ++ } ++ // CraftBukkit end ++ } ++ + protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {} + + public long getLootTableSeed() { +@@ -1612,19 +2022,35 @@ + } + + public void knockback(double strength, double x, double z) { +- strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); +- if (strength > 0.0D) { +- this.hasImpulse = true; ++ // CraftBukkit start - EntityKnockbackEvent ++ this.knockback(strength, x, z, null, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.UNKNOWN); // Paper - knockback events ++ } + ++ public void knockback(double d0, double d1, double d2, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events ++ d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); ++ if (true || d0 > 0.0D) { // CraftBukkit - Call event even when force is 0 ++ //this.hasImpulse = true; // CraftBukkit - Move down ++ + Vec3 vec3d; + +- for (vec3d = this.getDeltaMovement(); x * x + z * z < 9.999999747378752E-6D; z = (Math.random() - Math.random()) * 0.01D) { +- x = (Math.random() - Math.random()) * 0.01D; ++ for (vec3d = this.getDeltaMovement(); d1 * d1 + d2 * d2 < 9.999999747378752E-6D; d2 = (Math.random() - Math.random()) * 0.01D) { ++ d1 = (Math.random() - Math.random()) * 0.01D; + } + +- Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength); ++ Vec3 vec3d1 = (new Vec3(d1, 0.0D, d2)).normalize().scale(d0); + +- this.setDeltaMovement(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ // Paper start - knockback events ++ Vec3 finalVelocity = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ Vec3 diff = finalVelocity.subtract(vec3d); ++ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, cause, d0, diff); ++ // Paper end - knockback events ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ this.hasImpulse = true; ++ this.setDeltaMovement(vec3d.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events ++ // CraftBukkit end + } + } + +@@ -1683,6 +2109,20 @@ + return new LivingEntity.Fallsounds(SoundEvents.GENERIC_SMALL_FALL, SoundEvents.GENERIC_BIG_FALL); + } + ++ // CraftBukkit start - Add delegate methods ++ public SoundEvent getHurtSound0(DamageSource damagesource) { ++ return this.getHurtSound(damagesource); ++ } ++ ++ public SoundEvent getDeathSound0() { ++ return this.getDeathSound(); ++ } ++ ++ public SoundEvent getFallDamageSound0(int fallHeight) { ++ return this.getFallDamageSound(fallHeight); ++ } ++ // CraftBukkit end ++ + public Optional getLastClimbablePos() { + return this.lastClimbablePos; + } +@@ -1718,7 +2158,7 @@ + + @Override + public boolean isAlive() { +- return !this.isRemoved() && this.getHealth() > 0.0F; ++ return !this.isRemoved() && this.getHealth() > 0.0F && !this.dead; // Paper - Check this.dead + } + + public boolean isLookingAtMe(LivingEntity entity, double d0, boolean flag, boolean visualShape, double... checkedYs) { +@@ -1757,9 +2197,14 @@ + int i = this.calculateFallDamage(fallDistance, damageMultiplier); + + if (i > 0) { ++ // CraftBukkit start ++ if (!this.hurtServer((ServerLevel) this.level(), damageSource, (float) i)) { ++ return true; ++ } ++ // CraftBukkit end + this.playSound(this.getFallDamageSound(i), 1.0F, 1.0F); + this.playBlockFallSound(); +- this.hurt(damageSource, (float) i); ++ // this.damageEntity(damagesource, (float) i); // CraftBukkit - moved up + return true; + } else { + return flag; +@@ -1830,7 +2275,7 @@ + + protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) { + if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) { +- this.hurtArmor(source, amount); ++ // this.hurtArmor(damagesource, f); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage + amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); + } + +@@ -1841,7 +2286,8 @@ + if (source.is(DamageTypeTags.BYPASSES_EFFECTS)) { + return amount; + } else { +- if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !source.is(DamageTypeTags.BYPASSES_RESISTANCE)) { ++ // CraftBukkit - Moved to handleEntityDamage(DamageSource, float) ++ if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !source.is(DamageTypeTags.BYPASSES_RESISTANCE)) { + int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5; + int j = 25 - i; + float f1 = amount * (float) j; +@@ -1884,18 +2330,170 @@ + } + } + +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { +- if (!this.isInvulnerableTo(world, source)) { +- amount = this.getDamageAfterArmorAbsorb(source, amount); +- amount = this.getDamageAfterMagicAbsorb(source, amount); +- float f1 = amount; ++ // CraftBukkit start ++ private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float f, final float invulnerabilityRelatedLastDamage) { // Paper - fix invulnerability reduction in EntityDamageEvent ++ float originalDamage = f; ++ // Paper start - fix invulnerability reduction in EntityDamageEvent ++ final com.google.common.base.Function invulnerabilityReductionEquation = d -> { ++ if (invulnerabilityRelatedLastDamage == 0) return 0D; // no last damage, no reduction ++ // last damage existed, this means the reduction *technically* is (new damage - last damage). ++ // If the event damage was changed to something less than invul damage, hard lock it at 0. ++ if (d < invulnerabilityRelatedLastDamage) return 0D; ++ return (double) -invulnerabilityRelatedLastDamage; ++ }; ++ final float originalInvulnerabilityReduction = invulnerabilityReductionEquation.apply((double) f).floatValue(); ++ f += originalInvulnerabilityReduction; ++ // Paper end - fix invulnerability reduction in EntityDamageEvent + +- amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F); +- this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - amount)); +- float f2 = f1 - amount; ++ com.google.common.base.Function freezing = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ if (damagesource.is(DamageTypeTags.IS_FREEZING) && LivingEntity.this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) { ++ return -(f - (f * 5.0F)); ++ } ++ return -0.0; ++ } ++ }; ++ float freezingModifier = freezing.apply((double) f).floatValue(); ++ f += freezingModifier; + ++ com.google.common.base.Function hardHat = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !LivingEntity.this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { ++ return -(f - (f * 0.75F)); ++ } ++ return -0.0; ++ } ++ }; ++ float hardHatModifier = hardHat.apply((double) f).floatValue(); ++ f += hardHatModifier; ++ ++ com.google.common.base.Function blocking = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0); ++ } ++ }; ++ float blockingModifier = blocking.apply((double) f).floatValue(); ++ f += blockingModifier; ++ ++ com.google.common.base.Function armor = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(f - LivingEntity.this.getDamageAfterArmorAbsorb(damagesource, f.floatValue())); ++ } ++ }; ++ float armorModifier = armor.apply((double) f).floatValue(); ++ f += armorModifier; ++ ++ com.google.common.base.Function resistance = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ if (!damagesource.is(DamageTypeTags.BYPASSES_EFFECTS) && LivingEntity.this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damagesource.is(DamageTypeTags.BYPASSES_RESISTANCE)) { ++ int i = (LivingEntity.this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5; ++ int j = 25 - i; ++ float f1 = f.floatValue() * (float) j; ++ ++ return -(f - Math.max(f1 / 25.0F, 0.0F)); ++ } ++ return -0.0; ++ } ++ }; ++ float resistanceModifier = resistance.apply((double) f).floatValue(); ++ f += resistanceModifier; ++ ++ com.google.common.base.Function magic = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(f - LivingEntity.this.getDamageAfterMagicAbsorb(damagesource, f.floatValue())); ++ } ++ }; ++ float magicModifier = magic.apply((double) f).floatValue(); ++ f += magicModifier; ++ ++ com.google.common.base.Function absorption = new com.google.common.base.Function() { ++ @Override ++ public Double apply(Double f) { ++ return -(Math.max(f - Math.max(f - LivingEntity.this.getAbsorptionAmount(), 0.0F), 0.0F)); ++ } ++ }; ++ float absorptionModifier = absorption.apply((double) f).floatValue(); ++ ++ // Paper start - fix invulnerability reduction in EntityDamageEvent ++ return CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption, (damageModifierDoubleMap, damageModifierFunctionMap) -> { ++ damageModifierFunctionMap.put(DamageModifier.INVULNERABILITY_REDUCTION, invulnerabilityReductionEquation); ++ damageModifierDoubleMap.put(DamageModifier.INVULNERABILITY_REDUCTION, (double) originalInvulnerabilityReduction); ++ }); ++ // Paper end - fix invulnerability reduction in EntityDamageEvent ++ } ++ ++ protected boolean actuallyHurt(ServerLevel worldserver, final DamageSource damagesource, float f, final EntityDamageEvent event) { // void -> boolean, add final ++ if (!this.isInvulnerableTo(worldserver, damagesource)) { ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { ++ // Paper start - PlayerAttackEntityCooldownResetEvent ++ //((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { ++ player.resetAttackStrengthTicker(); ++ } ++ } else { ++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); ++ } ++ // Paper end - PlayerAttackEntityCooldownResetEvent ++ } ++ ++ // Resistance ++ if (event.getDamage(DamageModifier.RESISTANCE) < 0) { ++ float f3 = (float) -event.getDamage(DamageModifier.RESISTANCE); ++ if (f3 > 0.0F && f3 < 3.4028235E37F) { ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F)); ++ } else if (damagesource.getEntity() instanceof ServerPlayer) { ++ ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F)); ++ } ++ } ++ } ++ ++ // Apply damage to helmet ++ if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { ++ this.hurtHelmet(damagesource, f); ++ } ++ ++ // Apply damage to armor ++ if (!damagesource.is(DamageTypeTags.BYPASSES_ARMOR)) { ++ float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT)); ++ this.hurtArmor(damagesource, armorDamage); ++ } ++ ++ // Apply blocking code // PAIL: steal from above ++ if (event.getDamage(DamageModifier.BLOCKING) < 0) { ++ this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); ++ Entity entity = damagesource.getDirectEntity(); ++ ++ if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency ++ this.blockUsingShield((LivingEntity) entity); ++ } ++ } ++ ++ boolean human = this instanceof net.minecraft.world.entity.player.Player; ++ float originalDamage = (float) event.getDamage(); ++ float absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION); ++ this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F)); ++ float f2 = absorptionModifier; ++ ++ if (f2 > 0.0F && f2 < 3.4028235E37F && this instanceof net.minecraft.world.entity.player.Player) { ++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F)); ++ } ++ // CraftBukkit end ++ + if (f2 > 0.0F && f2 < 3.4028235E37F) { +- Entity entity = source.getEntity(); ++ Entity entity = damagesource.getEntity(); + + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; +@@ -1904,13 +2502,48 @@ + } + } + +- if (amount != 0.0F) { +- this.getCombatTracker().recordDamage(source, amount); +- this.setHealth(this.getHealth() - amount); +- this.setAbsorptionAmount(this.getAbsorptionAmount() - amount); ++ // CraftBukkit start ++ if (f > 0 || !human) { ++ if (human) { ++ // PAIL: Be sure to drag all this code from the EntityHuman subclass each update. ++ ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damagesource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent ++ if (f < 3.4028235E37F) { ++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F)); ++ } ++ } ++ // CraftBukkit end ++ this.getCombatTracker().recordDamage(damagesource, f); ++ this.setHealth(this.getHealth() - f); ++ // CraftBukkit start ++ if (!human) { ++ this.setAbsorptionAmount(this.getAbsorptionAmount() - f); ++ } + this.gameEvent(GameEvent.ENTITY_DAMAGE); ++ ++ return true; ++ } else { ++ // Duplicate triggers if blocking ++ if (event.getDamage(DamageModifier.BLOCKING) < 0) { ++ if (this instanceof ServerPlayer) { ++ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order ++ f2 = (float) -event.getDamage(DamageModifier.BLOCKING); ++ if (f2 > 0.0F && f2 < 3.4028235E37F) { ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); ++ } ++ } ++ ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order ++ } ++ ++ return !io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.skipVanillaDamageTickWhenShieldBlocked; // Paper - this should always return true, however expose an unsupported setting to flip this to false to enable "shield stunning". ++ } else { ++ return true; // Paper - return false ONLY if event was cancelled ++ } ++ // CraftBukkit end + } + } ++ return true; // CraftBukkit // Paper - return false ONLY if event was cancelled + } + + public CombatTracker getCombatTracker() { +@@ -1935,8 +2568,18 @@ + } + + public final void setArrowCount(int stuckArrowCount) { +- this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, stuckArrowCount); ++ // CraftBukkit start ++ this.setArrowCount(stuckArrowCount, false); ++ } ++ ++ public final void setArrowCount(int i, boolean flag) { ++ ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, this.getArrowCount(), i, flag); ++ if (event.isCancelled()) { ++ return; ++ } ++ this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, event.getNewAmount()); + } ++ // CraftBukkit end + + public final int getStingerCount() { + return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID); +@@ -1999,7 +2642,7 @@ + this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); + } + +- if (!(this instanceof Player)) { ++ if (!(this instanceof net.minecraft.world.entity.player.Player)) { + this.setHealth(0.0F); + this.die(this.damageSources().generic()); + } +@@ -2083,7 +2726,7 @@ + + @Override + protected void onBelowWorld() { +- this.hurt(this.damageSources().fellOutOfWorld(), 4.0F); ++ this.hurt(this.damageSources().fellOutOfWorld(), this.level().getWorld().getVoidDamageAmount()); // Paper - use configured void damage amount + } + + protected void updateSwingTime() { +@@ -2182,6 +2825,12 @@ + + public abstract ItemStack getItemBySlot(EquipmentSlot slot); + ++ // CraftBukkit start ++ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) { ++ this.setItemSlot(enumitemslot, itemstack); ++ } ++ // CraftBukkit end ++ + public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack); + + public Iterable getHandSlots() { +@@ -2292,17 +2941,29 @@ + return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; + } + ++ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits + @VisibleForTesting + public void jumpFromGround() { + float f = this.getJumpPower(); + + if (f > 1.0E-5F) { + Vec3 vec3d = this.getDeltaMovement(); ++ // Paper start - Prevent excessive velocity through repeated crits ++ long time = System.nanoTime(); ++ boolean canCrit = true; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ canCrit = false; ++ if (time - this.lastJumpTime > (long)(0.250e9)) { ++ this.lastJumpTime = time; ++ canCrit = true; ++ } ++ } ++ // Paper end - Prevent excessive velocity through repeated crits + + this.setDeltaMovement(vec3d.x, Math.max((double) f, vec3d.y), vec3d.z); + if (this.isSprinting()) { + float f1 = this.getYRot() * 0.017453292F; +- ++ if (canCrit) // Paper - Prevent excessive velocity through repeated crits + this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D)); + } + +@@ -2494,7 +3155,7 @@ + + } + +- private void travelRidden(Player controllingPlayer, Vec3 movementInput) { ++ private void travelRidden(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) { + Vec3 vec3d1 = this.getRiddenInput(controllingPlayer, movementInput); + + this.tickRidden(controllingPlayer, vec3d1); +@@ -2507,13 +3168,13 @@ + + } + +- protected void tickRidden(Player controllingPlayer, Vec3 movementInput) {} ++ protected void tickRidden(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) {} + +- protected Vec3 getRiddenInput(Player controllingPlayer, Vec3 movementInput) { ++ protected Vec3 getRiddenInput(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) { + return movementInput; + } + +- protected float getRiddenSpeed(Player controllingPlayer) { ++ protected float getRiddenSpeed(net.minecraft.world.entity.player.Player controllingPlayer) { + return this.getSpeed(); + } + +@@ -2571,7 +3232,7 @@ + double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D); + double d2 = Math.max(motion.y, -0.15000000596046448D); + +- if (d2 < 0.0D && !this.getInBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof Player) { ++ if (d2 < 0.0D && !this.getInBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof net.minecraft.world.entity.player.Player) { + d2 = 0.0D; + } + +@@ -2586,7 +3247,7 @@ + } + + protected float getFlyingSpeed() { +- return this.getControllingPassenger() instanceof Player ? this.getSpeed() * 0.1F : 0.02F; ++ return this.getControllingPassenger() instanceof net.minecraft.world.entity.player.Player ? this.getSpeed() * 0.1F : 0.02F; + } + + public float getSpeed() { +@@ -2634,7 +3295,7 @@ + } + } + +- this.detectEquipmentUpdates(); ++ this.detectEquipmentUpdatesPublic(); // CraftBukkit + if (this.tickCount % 20 == 0) { + this.getCombatTracker().recheckStatus(); + } +@@ -2687,38 +3348,16 @@ + gameprofilerfiller.pop(); + gameprofilerfiller.push("rangeChecks"); + +- while (this.getYRot() - this.yRotO < -180.0F) { +- this.yRotO -= 360.0F; +- } ++ // Paper start - stop large pitch and yaw changes from crashing the server ++ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; + +- while (this.getYRot() - this.yRotO >= 180.0F) { +- this.yRotO += 360.0F; +- } ++ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO < -180.0F) { +- this.yBodyRotO -= 360.0F; +- } ++ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { +- this.yBodyRotO += 360.0F; +- } +- +- while (this.getXRot() - this.xRotO < -180.0F) { +- this.xRotO -= 360.0F; +- } +- +- while (this.getXRot() - this.xRotO >= 180.0F) { +- this.xRotO += 360.0F; +- } ++ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; ++ // Paper end + +- while (this.yHeadRot - this.yHeadRotO < -180.0F) { +- this.yHeadRotO -= 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { +- this.yHeadRotO += 360.0F; +- } +- + gameprofilerfiller.pop(); + this.animStep += f2; + if (this.isFallFlying()) { +@@ -2741,7 +3380,7 @@ + this.elytraAnimationState.tick(); + } + +- public void detectEquipmentUpdates() { ++ public void detectEquipmentUpdatesPublic() { // CraftBukkit + Map map = this.collectEquipmentChanges(); + + if (map != null) { +@@ -2778,10 +3417,17 @@ + throw new MatchException((String) null, (Throwable) null); + } + +- ItemStack itemstack2 = itemstack1; ++ ItemStack itemstack2 = itemstack1; final ItemStack oldEquipment = itemstack2; // Paper - PlayerArmorChangeEvent - obfhelper + +- itemstack = this.getItemBySlot(enumitemslot); ++ itemstack = this.getItemBySlot(enumitemslot); final ItemStack newEquipment = itemstack;// Paper - PlayerArmorChangeEvent - obfhelper + if (this.equipmentHasChanged(itemstack2, itemstack)) { ++ // Paper start - PlayerArmorChangeEvent ++ if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { ++ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(oldEquipment); ++ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(newEquipment); ++ new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent(); ++ } ++ // Paper end - PlayerArmorChangeEvent + if (map == null) { + map = Maps.newEnumMap(EquipmentSlot.class); + } +@@ -2864,7 +3510,7 @@ + } + + }); +- ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); ++ ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization + } + + private ItemStack getLastArmorItem(EquipmentSlot slot) { +@@ -2974,8 +3620,10 @@ + } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { + this.jumpInLiquid(FluidTags.LAVA); + } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); + this.noJumpDelay = 10; ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } else { + this.noJumpDelay = 0; +@@ -3000,7 +3648,7 @@ + { + LivingEntity entityliving = this.getControllingPassenger(); + +- if (entityliving instanceof Player entityhuman) { ++ if (entityliving instanceof net.minecraft.world.entity.player.Player entityhuman) { + if (this.isAlive()) { + this.travelRidden(entityhuman, vec3d1); + break label112; +@@ -3017,7 +3665,7 @@ + this.calculateEntityAnimation(this instanceof FlyingAnimal); + gameprofilerfiller.pop(); + gameprofilerfiller.push("freezing"); +- if (!this.level().isClientSide && !this.isDeadOrDying()) { ++ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API + int i = this.getTicksFrozen(); + + if (this.isInPowderSnow && this.canFreeze()) { +@@ -3046,6 +3694,20 @@ + + this.pushEntities(); + gameprofilerfiller.pop(); ++ // Paper start - Add EntityMoveEvent ++ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { ++ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { ++ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); ++ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end - Add EntityMoveEvent + world = this.level(); + if (world instanceof ServerLevel worldserver) { + if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { +@@ -3063,6 +3725,7 @@ + this.checkSlowFallDistance(); + if (!this.level().isClientSide) { + if (!this.canGlide()) { ++ if (this.getSharedFlag(7) != false && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit + this.setSharedFlag(7, false); + return; + } +@@ -3113,12 +3776,26 @@ + Level world = this.level(); + + if (!(world instanceof ServerLevel worldserver)) { +- this.level().getEntities(EntityTypeTest.forClass(Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); ++ this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); + } else { +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); ++ // Paper start - don't run getEntities if we're not going to use its result ++ if (!this.isPushable()) { ++ return; ++ } ++ net.minecraft.world.scores.Team team = this.getTeam(); ++ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) { ++ return; ++ } + ++ int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) { ++ return; ++ } ++ // Paper end - don't run getEntities if we're not going to use its result ++ List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule ++ + if (!list.isEmpty()) { +- int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ // Paper - don't run getEntities if we're not going to use its result; moved up + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { + int j = 0; +@@ -3138,10 +3815,12 @@ + } + + Iterator iterator1 = list.iterator(); ++ this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions); // Paper - Cap entity collisions + +- while (iterator1.hasNext()) { ++ while (iterator1.hasNext() && this.numCollisions < this.level().paperConfig().collisions.maxEntityCollisions) { // Paper - Cap entity collisions + Entity entity1 = (Entity) iterator1.next(); +- ++ entity1.numCollisions++; // Paper - Cap entity collisions ++ this.numCollisions++; // Paper - Cap entity collisions + this.doPush(entity1); + } + } +@@ -3190,10 +3869,16 @@ + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity entity = this.getVehicle(); + +- super.stopRiding(); +- if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation ++ if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen + this.dismountVehicle(entity); + } + +@@ -3258,7 +3943,7 @@ + } + + public void onItemPickup(ItemEntity item) { +- Entity entity = item.getOwner(); ++ Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate + + if (entity instanceof ServerPlayer) { + CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); +@@ -3268,7 +3953,7 @@ + + public void take(Entity item, int count) { + if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) { +- ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); ++ ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source + } + + } +@@ -3284,7 +3969,8 @@ + Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); + Vec3 vec3d1 = new Vec3(entity.getX(), entityY, entity.getZ()); + +- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; ++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists ++ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared + } + } + +@@ -3305,13 +3991,27 @@ + + @Override + public boolean isPickable() { +- return !this.isRemoved(); ++ return !this.isRemoved() && this.collides; // CraftBukkit + } + ++ // Paper start - Climbing should not bypass cramming gamerule + @Override + public boolean isPushable() { +- return this.isAlive() && !this.isSpectator() && !this.onClimbable(); ++ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit ++ // Paper end - Climbing should not bypass cramming gamerule ++ } ++ ++ // CraftBukkit start - collidable API ++ @Override ++ public boolean canCollideWithBukkit(Entity entity) { ++ return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID()); + } ++ // CraftBukkit end + + @Override + public float getYHeadRot() { +@@ -3342,7 +4042,7 @@ + } + + public final void setAbsorptionAmount(float absorptionAmount) { +- this.internalSetAbsorptionAmount(Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption())); ++ this.internalSetAbsorptionAmount(!Float.isNaN(absorptionAmount) ? Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption()) : 0.0F); // Paper - Check for NaN + } + + protected void internalSetAbsorptionAmount(float absorptionAmount) { +@@ -3367,6 +4067,11 @@ + return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; + } + ++ // Paper start - Properly cancel usable items ++ public void resyncUsingItem(ServerPlayer serverPlayer) { ++ this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer); ++ } ++ // Paper end - Properly cancel usable items + private void updatingUsingItem() { + if (this.isUsingItem()) { + if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { +@@ -3410,9 +4115,14 @@ + } + + public void startUsingItem(InteractionHand hand) { ++ // Paper start - Prevent consuming the wrong itemstack ++ this.startUsingItem(hand, false); ++ } ++ public void startUsingItem(InteractionHand hand, boolean forceUpdate) { ++ // Paper end - Prevent consuming the wrong itemstack + ItemStack itemstack = this.getItemInHand(hand); + +- if (!itemstack.isEmpty() && !this.isUsingItem()) { ++ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack + this.useItem = itemstack; + this.useItemRemaining = itemstack.getUseDuration(this); + if (!this.level().isClientSide) { +@@ -3483,13 +4193,50 @@ + this.releaseUsingItem(); + } else { + if (!this.useItem.isEmpty() && this.isUsingItem()) { +- ItemStack itemstack = this.useItem.finishUsingItem(this.level(), this); ++ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack ++ // CraftBukkit start - fire PlayerItemConsumeEvent ++ ItemStack itemstack; ++ PlayerItemConsumeEvent event = null; // Paper ++ if (this instanceof ServerPlayer entityPlayer) { ++ org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem); ++ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand); ++ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ // Update client ++ Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE); ++ if (consumable != null) { ++ consumable.cancelUsingItem(entityPlayer, this.useItem); ++ } ++ entityPlayer.getBukkitEntity().updateInventory(); ++ entityPlayer.getBukkitEntity().updateScaledHealth(); ++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use ++ return; ++ } + ++ itemstack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level(), this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(this.level(), this); ++ } else { ++ itemstack = this.useItem.finishUsingItem(this.level(), this); ++ } ++ // Paper start - save the default replacement item and change it if necessary ++ final ItemStack defaultReplacement = itemstack; ++ if (event != null && event.getReplacement() != null) { ++ itemstack = CraftItemStack.asNMSCopy(event.getReplacement()); ++ } ++ // Paper end ++ // CraftBukkit end ++ + if (itemstack != this.useItem) { + this.setItemInHand(enumhand, itemstack); + } + + this.stopUsingItem(); ++ // Paper start ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer) this).getBukkitEntity().updateInventory(); ++ } ++ // Paper end + } + + } +@@ -3512,6 +4259,7 @@ + + public void releaseUsingItem() { + if (!this.useItem.isEmpty()) { ++ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent + this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); + if (this.useItem.useOnRelease()) { + this.updatingUsingItem(); +@@ -3544,12 +4292,69 @@ + if (this.isUsingItem() && !this.useItem.isEmpty()) { + Item item = this.useItem.getItem(); + +- return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < 5 ? null : this.useItem); ++ return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < getShieldBlockingDelay() ? null : this.useItem); // Paper - Make shield blocking delay configurable + } else { + return null; + } ++ } ++ ++ // Paper start - Make shield blocking delay configurable ++ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); ++ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); ++ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); ++ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); ++ ++ return this.level().clip(raytrace); ++ } ++ ++ public @Nullable net.minecraft.world.phys.EntityHitResult getTargetEntity(int maxDistance) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = this.getEyePosition(1.0F); ++ Vec3 direction = this.getLookAngle(); ++ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); ++ ++ List entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable)); ++ ++ double distance = 0.0D; ++ net.minecraft.world.phys.EntityHitResult result = null; ++ ++ for (Entity entity : entityList) { ++ final double inflationAmount = (double) entity.getPickRadius(); ++ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount); ++ Optional rayTraceResult = aabb.clip(start, end); ++ ++ if (rayTraceResult.isPresent()) { ++ Vec3 rayTrace = rayTraceResult.get(); ++ double distanceTo = start.distanceToSqr(rayTrace); ++ if (distanceTo < distance || distance == 0.0D) { ++ result = new net.minecraft.world.phys.EntityHitResult(entity, rayTrace); ++ distance = distanceTo; ++ } ++ } ++ } ++ ++ return result; ++ } ++ ++ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; ++ ++ public int getShieldBlockingDelay() { ++ return shieldBlockingDelay; + } + ++ public void setShieldBlockingDelay(int shieldBlockingDelay) { ++ this.shieldBlockingDelay = shieldBlockingDelay; ++ } ++ // Paper end - Make shield blocking delay configurable ++ + public boolean isSuppressingSlidingDownLadder() { + return this.isShiftKeyDown(); + } +@@ -3568,12 +4373,18 @@ + } + + public boolean randomTeleport(double x, double y, double z, boolean particleEffects) { ++ // CraftBukkit start ++ return this.randomTeleport(x, y, z, particleEffects, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false); ++ } ++ ++ public Optional randomTeleport(double d0, double d1, double d2, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ // CraftBukkit end + double d3 = this.getX(); + double d4 = this.getY(); + double d5 = this.getZ(); +- double d6 = y; ++ double d6 = d1; + boolean flag1 = false; +- BlockPos blockposition = BlockPos.containing(x, y, z); ++ BlockPos blockposition = BlockPos.containing(d0, d1, d2); + Level world = this.level(); + + if (world.hasChunkAt(blockposition)) { +@@ -3592,18 +4403,43 @@ + } + + if (flag2) { +- this.teleportTo(x, d6, z); ++ // CraftBukkit start - Teleport event ++ // this.teleportTo(d0, d6, d2); ++ ++ // first set position, to check if the place to teleport is valid ++ this.setPos(d0, d6, d2); + if (world.noCollision((Entity) this) && !world.containsAnyLiquid(this.getBoundingBox())) { + flag1 = true; + } ++ // now revert and call event if the teleport place is valid ++ this.setPos(d3, d4, d5); ++ ++ if (flag1) { ++ if (!(this instanceof ServerPlayer)) { ++ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2)); ++ this.level().getCraftServer().getPluginManager().callEvent(teleport); ++ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper ++ Location to = teleport.getTo(); ++ this.teleportTo(to.getX(), to.getY(), to.getZ()); ++ } else { ++ return Optional.empty(); ++ } ++ } else { ++ // player teleport event is called in the underlining code ++ if (!((ServerPlayer) this).connection.teleport(d0, d6, d2, this.getYRot(), this.getXRot(), cause)) { ++ return Optional.empty(); ++ } ++ } ++ } ++ // CraftBukkit end + } + } + + if (!flag1) { +- this.teleportTo(d3, d4, d5); +- return false; ++ // this.enderTeleportTo(d3, d4, d5); // CraftBukkit - already set the location back ++ return Optional.of(false); // CraftBukkit + } else { +- if (particleEffects) { ++ if (flag) { + world.broadcastEntityEvent(this, (byte) 46); + } + +@@ -3613,7 +4449,7 @@ + entitycreature.getNavigation().stop(); + } + +- return true; ++ return Optional.of(true); // CraftBukkit + } + } + +@@ -3706,7 +4542,7 @@ + } + + public void stopSleeping() { +- Optional optional = this.getSleepingPos(); ++ Optional optional = this.getSleepingPos(); // CraftBukkit - decompile error + Level world = this.level(); + + java.util.Objects.requireNonNull(world); +@@ -3718,9 +4554,9 @@ + + this.level().setBlock(blockposition, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, false), 3); + Vec3 vec3d = (Vec3) BedBlock.findStandUpPosition(this.getType(), this.level(), blockposition, enumdirection, this.getYRot()).orElseGet(() -> { +- BlockPosition blockposition1 = blockposition.above(); ++ BlockPos blockposition1 = blockposition.above(); + +- return new Vec3D((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.1D, (double) blockposition1.getZ() + 0.5D); ++ return new Vec3((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.1D, (double) blockposition1.getZ() + 0.5D); + }); + Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize(); + float f = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); +@@ -3740,7 +4576,7 @@ + + @Nullable + public Direction getBedOrientation() { +- BlockPos blockposition = (BlockPos) this.getSleepingPos().orElse((Object) null); ++ BlockPos blockposition = (BlockPos) this.getSleepingPos().orElse(null); // CraftBukkit - decompile error + + return blockposition != null ? BedBlock.getBedOrientation(this.level(), blockposition) : null; + } +@@ -3905,7 +4741,7 @@ + public float maxUpStep() { + float f = (float) this.getAttributeValue(Attributes.STEP_HEIGHT); + +- return this.getControllingPassenger() instanceof Player ? Math.max(f, 1.0F) : f; ++ return this.getControllingPassenger() instanceof net.minecraft.world.entity.player.Player ? Math.max(f, 1.0F) : f; + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch new file mode 100644 index 0000000000..aeaafc3a1b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch @@ -0,0 +1,472 @@ +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -84,6 +84,17 @@ + import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.AABB; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++import org.bukkit.event.entity.EntityUnleashEvent; ++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++// CraftBukkit end + + public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting { + +@@ -112,6 +123,7 @@ + private final BodyRotationControl bodyRotationControl; + protected PathNavigation navigation; + public GoalSelector goalSelector; ++ @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float + public GoalSelector targetSelector; + @Nullable + private LivingEntity target; +@@ -132,6 +144,8 @@ + private BlockPos restrictCenter; + private float restrictRadius; + ++ public boolean aware = true; // CraftBukkit ++ + protected Mob(EntityType type, Level world) { + super(type, world); + this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); +@@ -157,8 +171,14 @@ + if (world instanceof ServerLevel) { + this.registerGoals(); + } ++ ++ } + ++ // CraftBukkit start ++ public void setPersistenceRequired(boolean persistenceRequired) { ++ this.persistenceRequired = persistenceRequired; + } ++ // CraftBukkit end + + protected void registerGoals() {} + +@@ -264,13 +284,44 @@ + + @Nullable + protected final LivingEntity getTargetFromBrain() { +- return (LivingEntity) this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse((Object) null); ++ return (LivingEntity) this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); // CraftBukkit - decompile error + } + + public void setTarget(@Nullable LivingEntity target) { +- this.target = target; ++ // CraftBukkit start - fire event ++ this.setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true); + } + ++ public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { ++ if (this.getTarget() == entityliving) return false; ++ if (fireEvent) { ++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN && this.getTarget() != null && entityliving == null) { ++ reason = this.getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED; ++ } ++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN) { ++ this.level().getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception()); ++ } ++ CraftLivingEntity ctarget = null; ++ if (entityliving != null) { ++ ctarget = (CraftLivingEntity) entityliving.getBukkitEntity(); ++ } ++ EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ if (event.getTarget() != null) { ++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle(); ++ } else { ++ entityliving = null; ++ } ++ } ++ this.target = entityliving; ++ return true; ++ // CraftBukkit end ++ } ++ + @Override + public boolean canAttackType(EntityType type) { + return type != EntityType.GHAST; +@@ -399,6 +450,12 @@ + return null; + } + ++ // CraftBukkit start - Add delegate method ++ public SoundEvent getAmbientSound0() { ++ return this.getAmbientSound(); ++ } ++ // CraftBukkit end ++ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); +@@ -473,13 +530,25 @@ + nbt.putBoolean("NoAI", this.isNoAi()); + } + ++ nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit + } + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); +- this.setCanPickUpLoot(nbt.getBoolean("CanPickUpLoot")); +- this.persistenceRequired = nbt.getBoolean("PersistenceRequired"); ++ // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it ++ if (nbt.contains("CanPickUpLoot", 99)) { ++ boolean data = nbt.getBoolean("CanPickUpLoot"); ++ if (isLevelAtLeast(nbt, 1) || data) { ++ this.setCanPickUpLoot(data); ++ } ++ } ++ ++ boolean data = nbt.getBoolean("PersistenceRequired"); ++ if (isLevelAtLeast(nbt, 1) || data) { ++ this.persistenceRequired = data; ++ } ++ // CraftBukkit end + ListTag nbttaglist; + CompoundTag nbttagcompound1; + int i; +@@ -540,13 +609,18 @@ + this.readLeashData(nbt); + this.setLeftHanded(nbt.getBoolean("LeftHanded")); + if (nbt.contains("DeathLootTable", 8)) { +- this.lootTable = Optional.of(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("DeathLootTable")))); ++ this.lootTable = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("DeathLootTable"))).map((rs) -> ResourceKey.create(Registries.LOOT_TABLE, rs)); // Paper - Validate ResourceLocation + } else { + this.lootTable = Optional.empty(); + } + + this.lootTableSeed = nbt.getLong("DeathLootTableSeed"); + this.setNoAi(nbt.getBoolean("NoAI")); ++ // CraftBukkit start ++ if (nbt.contains("Bukkit.Aware")) { ++ this.aware = nbt.getBoolean("Bukkit.Aware"); ++ } ++ // CraftBukkit end + } + + @Override +@@ -608,6 +682,11 @@ + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(worldserver, entityitem.getItem())) { ++ // Paper start - Item#canEntityPickup ++ if (!entityitem.canMobPickup) { ++ continue; ++ } ++ // Paper end - Item#canEntityPickup + this.pickUpItem(worldserver, entityitem); + } + } +@@ -623,23 +702,29 @@ + + protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) { + ItemStack itemstack = itemEntity.getItem(); +- ItemStack itemstack1 = this.equipItemIfPossible(world, itemstack.copy()); ++ ItemStack itemstack1 = this.equipItemIfPossible(world, itemstack.copy(), itemEntity); // CraftBukkit - add item + + if (!itemstack1.isEmpty()) { + this.onItemPickup(itemEntity); + this.take(itemEntity, itemstack1.getCount()); + itemstack.shrink(itemstack1.getCount()); + if (itemstack.isEmpty()) { +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } + } + + } + + public ItemStack equipItemIfPossible(ServerLevel world, ItemStack stack) { +- EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(stack); ++ // CraftBukkit start - add item ++ return this.equipItemIfPossible(world, stack, null); ++ } ++ ++ public ItemStack equipItemIfPossible(ServerLevel worldserver, ItemStack itemstack, ItemEntity entityitem) { ++ // CraftBukkit end ++ EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack); + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); +- boolean flag = this.canReplaceCurrentItem(stack, itemstack1, enumitemslot); ++ boolean flag = this.canReplaceCurrentItem(itemstack, itemstack1, enumitemslot); + + if (enumitemslot.isArmor() && !flag) { + enumitemslot = EquipmentSlot.MAINHAND; +@@ -647,14 +732,22 @@ + flag = itemstack1.isEmpty(); + } + +- if (flag && this.canHoldItem(stack)) { ++ // CraftBukkit start ++ boolean canPickup = flag && this.canHoldItem(itemstack); ++ if (entityitem != null) { ++ canPickup = !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entityitem, 0, !canPickup).isCancelled(); ++ } ++ if (canPickup) { ++ // CraftBukkit end + double d0 = (double) this.getEquipmentDropChance(enumitemslot); + + if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) { +- this.spawnAtLocation(world, itemstack1); ++ this.forceDrops = true; // CraftBukkit ++ this.spawnAtLocation(worldserver, itemstack1); ++ this.forceDrops = false; // CraftBukkit + } + +- ItemStack itemstack2 = enumitemslot.limit(stack); ++ ItemStack itemstack2 = enumitemslot.limit(itemstack); + + this.setItemSlotAndDropWhenKilled(enumitemslot, itemstack2); + return itemstack2; +@@ -768,25 +861,29 @@ + @Override + public void checkDespawn() { + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Player entityhuman = this.level().getNearestPlayer(this, -1.0D); ++ Player entityhuman = this.level().findNearbyPlayer(this, -1.0D, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API + + if (entityhuman != null) { +- double d0 = entityhuman.distanceToSqr((Entity) this); +- int i = this.getType().getCategory().getDespawnDistance(); +- int j = i * i; +- +- if (d0 > (double) j && this.removeWhenFarAway(d0)) { +- this.discard(); ++ // Paper start - Configurable despawn distances ++ final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); ++ final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape; ++ final double dy = Math.abs(entityhuman.getY() - this.getY()); ++ final double dySqr = Math.pow(dy, 2); ++ final double dxSqr = Math.pow(entityhuman.getX() - this.getX(), 2); ++ final double dzSqr = Math.pow(entityhuman.getZ() - this.getZ(), 2); ++ final double distanceSquared = dxSqr + dzSqr + dySqr; ++ // Despawn if hard/soft limit is exceeded ++ if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } +- +- int k = this.getType().getCategory().getNoDespawnDistance(); +- int l = k * k; +- +- if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > (double) l && this.removeWhenFarAway(d0)) { +- this.discard(); +- } else if (d0 < (double) l) { ++ if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) { ++ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } ++ } else { ++ // Paper end - Configurable despawn distances + this.noActionTime = 0; + } + } +@@ -799,6 +896,15 @@ + @Override + protected final void serverAiStep() { + ++this.noActionTime; ++ // Paper start - Allow nerfed mobs to jump and float ++ if (!this.aware) { ++ if (goalFloat != null) { ++ if (goalFloat.canUse()) goalFloat.tick(); ++ this.getJumpControl().tick(); ++ } ++ return; ++ } ++ // Paper end - Allow nerfed mobs to jump and float + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("sensing"); +@@ -994,23 +1100,36 @@ + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { ++ // Paper start - Fix silent equipment change ++ setItemSlot(slot, stack, false); ++ } ++ ++ @Override ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { ++ // Paper end - Fix silent equipment change + this.verifyEquippedItem(stack); + switch (slot.getType()) { + case HAND: +- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + break; + case HUMANOID_ARMOR: +- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + break; + case ANIMAL_ARMOR: + ItemStack itemstack1 = this.bodyArmorItem; + + this.bodyArmorItem = stack; +- this.onEquipItem(slot, itemstack1, stack); ++ this.onEquipItem(slot, itemstack1, stack, silent); // Paper - Fix silent equipment change + } + + } + ++ // Paper start ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox) ++ return false; ++ } ++ // Paper end ++ + @Override + protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { + super.dropCustomDeathLoot(world, source, causedByPlayer); +@@ -1018,6 +1137,7 @@ + + while (iterator.hasNext()) { + EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next(); ++ if (this.shouldSkipLoot(enumitemslot)) continue; // Paper + ItemStack itemstack = this.getItemBySlot(enumitemslot); + float f = this.getEquipmentDropChance(enumitemslot); + +@@ -1042,7 +1162,13 @@ + } + + this.spawnAtLocation(world, itemstack); ++ if (this.clearEquipmentSlots) { // Paper + this.setItemSlot(enumitemslot, ItemStack.EMPTY); ++ // Paper start ++ } else { ++ this.clearedEquipmentSlots.add(enumitemslot); ++ } ++ // Paper end + } + } + } +@@ -1338,7 +1464,7 @@ + if (itemstack.getItem() instanceof SpawnEggItem) { + if (this.level() instanceof ServerLevel) { + SpawnEggItem itemmonsteregg = (SpawnEggItem) itemstack.getItem(); +- Optional optional = itemmonsteregg.spawnOffspringFromSpawnEgg(player, this, this.getType(), (ServerLevel) this.level(), this.position(), itemstack); ++ Optional optional = itemmonsteregg.spawnOffspringFromSpawnEgg(player, this, (EntityType) this.getType(), (ServerLevel) this.level(), this.position(), itemstack); // CraftBukkit - decompile error + + optional.ifPresent((entityinsentient) -> { + this.onOffspringSpawnedFromEgg(player, entityinsentient); +@@ -1389,28 +1515,51 @@ + return this.restrictRadius != -1.0F; + } + ++ // CraftBukkit start + @Nullable + public T convertTo(EntityType entityType, ConversionParams context, EntitySpawnReason reason, ConversionParams.AfterConversion finalizer) { ++ return this.convertTo(entityType, context, reason, finalizer, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ @Nullable ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.AfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation ++ return this.convertTo(entitytypes, conversionparams, entityspawnreason, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason); ++ } ++ @Nullable ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.CancellingAfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper end - entity zap event - allow cancellation of conversion post creation ++ // CraftBukkit end + if (this.isRemoved()) { + return null; + } else { +- T t0 = (Mob) entityType.create(this.level(), reason); ++ T t0 = entitytypes.create(this.level(), EntitySpawnReason.CONVERSION); // CraftBukkit - decompile error + + if (t0 == null) { + return null; + } else { +- context.type().convert(this, t0, context); +- finalizer.finalizeConversion(t0); ++ conversionparams.type().convert(this, t0, conversionparams); ++ if (!conversionparams_a.finalizeConversionOrCancel(t0)) return null; // Paper - entity zap event - return null if conversion was cancelled + Level world = this.level(); + ++ // CraftBukkit start ++ if (transformReason == null) { ++ // Special handling for slime split and pig lightning ++ return t0; ++ } ++ ++ if (CraftEventFactory.callEntityTransformEvent(this, t0, transformReason).isCancelled()) { ++ return null; ++ } ++ // CraftBukkit end + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- worldserver.addFreshEntity(t0); ++ worldserver.addFreshEntity(t0, spawnReason); // CraftBukkit + } + +- if (context.type().shouldDiscardAfterConversion()) { +- this.discard(); ++ if (conversionparams.type().shouldDiscardAfterConversion()) { ++ this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause + } + + return t0; +@@ -1420,10 +1569,22 @@ + + @Nullable + public T convertTo(EntityType entityType, ConversionParams context, ConversionParams.AfterConversion finalizer) { +- return this.convertTo(entityType, context, EntitySpawnReason.CONVERSION, finalizer); ++ // CraftBukkit start ++ return this.convertTo(entityType, context, finalizer, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT); + } + + @Nullable ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, ConversionParams.AfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation ++ return this.convertTo(entitytypes, conversionparams, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason); ++ } ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, ConversionParams.CancellingAfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation ++ return this.convertTo(entitytypes, conversionparams, EntitySpawnReason.CONVERSION, conversionparams_a, transformReason, spawnReason); ++ // CraftBukkit end ++ } ++ ++ @Nullable + @Override + public Leashable.LeashData getLeashData() { + return this.leashData; +@@ -1458,7 +1619,15 @@ + boolean flag1 = super.startRiding(entity, force); + + if (flag1 && this.isLeashed()) { +- this.dropLeash(); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true); ++ if (!event.callEvent()) { return flag1; } ++ if (event.isDropLeash()) { ++ this.dropLeash(); ++ } else { ++ this.removeLeash(); ++ } ++ // Paper end - Expand EntityUnleashEvent + } + + return flag1; +@@ -1542,7 +1711,7 @@ + + if (f1 > 0.0F && target instanceof LivingEntity) { + entityliving = (LivingEntity) target; +- entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); ++ entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch new file mode 100644 index 0000000000..2088822118 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch @@ -0,0 +1,86 @@ +--- a/net/minecraft/world/entity/NeutralMob.java ++++ b/net/minecraft/world/entity/NeutralMob.java +@@ -8,6 +8,9 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public interface NeutralMob { + +@@ -42,24 +45,11 @@ + UUID uuid = nbt.getUUID("AngryAt"); + + this.setPersistentAngerTarget(uuid); +- Entity entity = ((ServerLevel) world).getEntity(uuid); +- +- if (entity != null) { +- if (entity instanceof Mob) { +- Mob entityinsentient = (Mob) entity; +- +- this.setTarget(entityinsentient); +- this.setLastHurtByMob(entityinsentient); +- } +- +- if (entity instanceof Player) { +- Player entityhuman = (Player) entity; +- +- this.setTarget(entityhuman); +- this.setLastHurtByPlayer(entityhuman); +- } +- +- } ++ // Paper - Prevent entity loading causing async lookups; Moved diff to separate method ++ // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively ++ // tick the initial persistent anger. ++ // If not, let the first tick on the baseTick call the method later down the line. ++ if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world); + } + } + } +@@ -114,7 +104,7 @@ + default void stopBeingAngry() { + this.setLastHurtByMob((LivingEntity) null); + this.setPersistentAngerTarget((UUID) null); +- this.setTarget((LivingEntity) null); ++ this.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit + this.setRemainingPersistentAngerTime(0); + } + +@@ -127,8 +117,34 @@ + + void setTarget(@Nullable LivingEntity target); + ++ boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent); // CraftBukkit ++ + boolean canAttack(LivingEntity target); + + @Nullable + LivingEntity getTarget(); ++ ++ // Paper start - Prevent entity loading causing async lookups ++ // Update last hurt when ticking ++ default void tickInitialPersistentAnger(Level level) { ++ UUID target = getPersistentAngerTarget(); ++ if (target == null) { ++ return; ++ } ++ ++ Entity entity = ((ServerLevel) level).getEntity(target); ++ ++ if (entity != null) { ++ if (entity instanceof Mob mob) { ++ this.setTarget(mob, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit ++ this.setLastHurtByMob(mob); ++ } ++ ++ if (entity instanceof Player player) { ++ this.setTarget(player, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit ++ this.setLastHurtByPlayer(player); ++ } ++ } ++ } ++ // Paper end - Prevent entity loading causing async lookups + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch new file mode 100644 index 0000000000..c390374ad2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/entity/OminousItemSpawner.java ++++ b/net/minecraft/world/entity/OminousItemSpawner.java +@@ -76,7 +76,7 @@ + entity = this.spawnProjectile(serverLevel, projectileItem, itemStack); + } else { + entity = new ItemEntity(serverLevel, this.getX(), this.getY(), this.getZ(), itemStack); +- serverLevel.addFreshEntity(entity); ++ serverLevel.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - fixes and addition to spawn reason API + } + + serverLevel.levelEvent(3021, this.blockPosition(), 1); +@@ -90,7 +90,7 @@ + ProjectileItem.DispenseConfig dispenseConfig = item.createDispenseConfig(); + dispenseConfig.overrideDispenseEvent().ifPresent(dispenseEvent -> world.levelEvent(dispenseEvent, this.blockPosition(), 0)); + Direction direction = Direction.DOWN; +- Projectile projectile = Projectile.spawnProjectileUsingShoot( ++ Projectile projectile = Projectile.spawnProjectileUsingShootDelayed( // Paper - fixes and addition to spawn reason API + item.asProjectile(world, this.position(), stack, direction), + world, + stack, +@@ -99,7 +99,7 @@ + (double)direction.getStepZ(), + dispenseConfig.power(), + dispenseConfig.uncertainty() +- ); ++ ).spawn(org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - fixes and addition to spawn reason API + projectile.setOwner(this); + return projectile; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/PathfinderMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/PathfinderMob.java.patch new file mode 100644 index 0000000000..24a880fc47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/PathfinderMob.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/entity/PathfinderMob.java ++++ b/net/minecraft/world/entity/PathfinderMob.java +@@ -10,6 +10,9 @@ + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityUnleashEvent; ++// CraftBukkit end + + public abstract class PathfinderMob extends Mob { + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch new file mode 100644 index 0000000000..aa779fc65d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/entity/Shearable.java ++++ b/net/minecraft/world/entity/Shearable.java +@@ -5,7 +5,15 @@ + import net.minecraft.world.item.ItemStack; + + public interface Shearable { ++ default void shear(ServerLevel world, SoundSource soundCategory, ItemStack shears, java.util.List drops) { this.shear(world, soundCategory, shears); } // Paper - Add drops to shear events + void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears); + + boolean readyForShearing(); ++ net.minecraft.world.level.Level level(); // Shearable API - expose default level needed for shearing. ++ ++ // Paper start - custom shear drops; ensure all implementing entities override this ++ default java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ return java.util.Collections.emptyList(); ++ } ++ // Paper end - custom shear drops + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch new file mode 100644 index 0000000000..ae8be7d5d0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch @@ -0,0 +1,85 @@ +--- a/net/minecraft/world/entity/TamableAnimal.java ++++ b/net/minecraft/world/entity/TamableAnimal.java +@@ -27,6 +27,11 @@ + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; + import net.minecraft.world.scores.PlayerTeam; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTeleportEvent; ++// CraftBukkit end + + public abstract class TamableAnimal extends Animal implements OwnableEntity { + +@@ -85,7 +90,7 @@ + } + + this.orderedToSit = nbt.getBoolean("Sitting"); +- this.setInSittingPose(this.orderedToSit); ++ this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent + } + + @Override +@@ -96,8 +101,16 @@ + @Override + public boolean handleLeashAtDistance(Entity leashHolder, float distance) { + if (this.isInSittingPose()) { +- if (distance > 10.0F) { +- this.dropLeash(); ++ if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return false; ++ if (event.isDropLeash()) { ++ this.dropLeash(); ++ } else { ++ this.removeLeash(); ++ } ++ // Paper end - Expand EntityUnleashEvent + } + + return false; +@@ -161,6 +174,12 @@ + } + + public void setInSittingPose(boolean inSittingPose) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setInSittingPose(inSittingPose, true); ++ } ++ public void setInSittingPose(boolean inSittingPose, boolean callEvent) { ++ if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + byte b0 = (Byte) this.entityData.get(TamableAnimal.DATA_FLAGS_ID); + + if (inSittingPose) { +@@ -244,7 +263,12 @@ + if (entityliving instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entityliving; + +- entityplayer.sendSystemMessage(this.getCombatTracker().getDeathMessage()); ++ // Paper start - Add TameableDeathMessageEvent ++ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage())); ++ if (event.callEvent()) { ++ entityplayer.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage())); ++ } ++ // Paper end - Add TameableDeathMessageEvent + } + } + } +@@ -295,7 +319,14 @@ + if (!this.canTeleportTo(new BlockPos(x, y, z))) { + return false; + } else { +- this.moveTo((double) x + 0.5D, (double) y, (double) z + 0.5D, this.getYRot(), this.getXRot()); ++ // CraftBukkit start ++ EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this, (double) x + 0.5D, (double) y, (double) z + 0.5D); ++ if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location ++ return false; ++ } ++ Location to = event.getTo(); ++ this.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); ++ // CraftBukkit end + this.navigation.stop(); + return true; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch new file mode 100644 index 0000000000..d6d3313220 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +@@ -153,20 +153,20 @@ + double d = this.getBaseValue(); + + for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) { +- d += attributeModifier.amount(); ++ d += attributeModifier.amount(); // Paper - destroy speed API - diff on change + } + + double e = d; + + for (AttributeModifier attributeModifier2 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) { +- e += d * attributeModifier2.amount(); ++ e += d * attributeModifier2.amount(); // Paper - destroy speed API - diff on change + } + + for (AttributeModifier attributeModifier3 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) { +- e *= 1.0 + attributeModifier3.amount(); ++ e *= 1.0 + attributeModifier3.amount(); // Paper - destroy speed API - diff on change + } + +- return this.attribute.value().sanitizeValue(e); ++ return attribute.value().sanitizeValue(e); // Paper - destroy speed API - diff on change + } + + private Collection getModifiersOrEmpty(AttributeModifier.Operation operation) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch new file mode 100644 index 0000000000..08b12ec883 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -162,4 +162,12 @@ + } + } + } ++ ++ // Paper - start - living entity allow attribute registration ++ public void registerAttribute(Holder attributeBase) { ++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); ++ attributes.put(attributeBase, attributeModifiable); ++ } ++ // Paper - end - living entity allow attribute registration ++ + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch new file mode 100644 index 0000000000..51565ac321 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/entity/ai/attributes/Attributes.java ++++ b/net/minecraft/world/entity/ai/attributes/Attributes.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.world.entity.ai.attributes; + + import net.minecraft.core.Holder; +@@ -9,7 +10,7 @@ + + public static final Holder ARMOR = Attributes.register("armor", (new RangedAttribute("attribute.name.armor", 0.0D, 0.0D, 30.0D)).setSyncable(true)); + public static final Holder ARMOR_TOUGHNESS = Attributes.register("armor_toughness", (new RangedAttribute("attribute.name.armor_toughness", 0.0D, 0.0D, 20.0D)).setSyncable(true)); +- public static final Holder ATTACK_DAMAGE = Attributes.register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0D, 0.0D, 2048.0D)); ++ public static final Holder ATTACK_DAMAGE = Attributes.register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0D, 0.0D, org.spigotmc.SpigotConfig.attackDamage)); + public static final Holder ATTACK_KNOCKBACK = Attributes.register("attack_knockback", new RangedAttribute("attribute.name.attack_knockback", 0.0D, 0.0D, 5.0D)); + public static final Holder ATTACK_SPEED = Attributes.register("attack_speed", (new RangedAttribute("attribute.name.attack_speed", 4.0D, 0.0D, 1024.0D)).setSyncable(true)); + public static final Holder BLOCK_BREAK_SPEED = Attributes.register("block_break_speed", (new RangedAttribute("attribute.name.block_break_speed", 1.0D, 0.0D, 1024.0D)).setSyncable(true)); +@@ -24,11 +25,11 @@ + public static final Holder JUMP_STRENGTH = Attributes.register("jump_strength", (new RangedAttribute("attribute.name.jump_strength", 0.41999998688697815D, 0.0D, 32.0D)).setSyncable(true)); + public static final Holder KNOCKBACK_RESISTANCE = Attributes.register("knockback_resistance", new RangedAttribute("attribute.name.knockback_resistance", 0.0D, 0.0D, 1.0D)); + public static final Holder LUCK = Attributes.register("luck", (new RangedAttribute("attribute.name.luck", 0.0D, -1024.0D, 1024.0D)).setSyncable(true)); +- public static final Holder MAX_ABSORPTION = Attributes.register("max_absorption", (new RangedAttribute("attribute.name.max_absorption", 0.0D, 0.0D, 2048.0D)).setSyncable(true)); +- public static final Holder MAX_HEALTH = Attributes.register("max_health", (new RangedAttribute("attribute.name.max_health", 20.0D, 1.0D, 1024.0D)).setSyncable(true)); ++ public static final Holder MAX_ABSORPTION = Attributes.register("max_absorption", (new RangedAttribute("attribute.name.max_absorption", 0.0D, 0.0D, org.spigotmc.SpigotConfig.maxAbsorption)).setSyncable(true)); ++ public static final Holder MAX_HEALTH = Attributes.register("max_health", (new RangedAttribute("attribute.name.max_health", 20.0D, 1.0D, org.spigotmc.SpigotConfig.maxHealth)).setSyncable(true)); + public static final Holder MINING_EFFICIENCY = Attributes.register("mining_efficiency", (new RangedAttribute("attribute.name.mining_efficiency", 0.0D, 0.0D, 1024.0D)).setSyncable(true)); + public static final Holder MOVEMENT_EFFICIENCY = Attributes.register("movement_efficiency", (new RangedAttribute("attribute.name.movement_efficiency", 0.0D, 0.0D, 1.0D)).setSyncable(true)); +- public static final Holder MOVEMENT_SPEED = Attributes.register("movement_speed", (new RangedAttribute("attribute.name.movement_speed", 0.7D, 0.0D, 1024.0D)).setSyncable(true)); ++ public static final Holder MOVEMENT_SPEED = Attributes.register("movement_speed", (new RangedAttribute("attribute.name.movement_speed", 0.7D, 0.0D, org.spigotmc.SpigotConfig.movementSpeed)).setSyncable(true)); + public static final Holder OXYGEN_BONUS = Attributes.register("oxygen_bonus", (new RangedAttribute("attribute.name.oxygen_bonus", 0.0D, 0.0D, 1024.0D)).setSyncable(true)); + public static final Holder SAFE_FALL_DISTANCE = Attributes.register("safe_fall_distance", (new RangedAttribute("attribute.name.safe_fall_distance", 3.0D, -1024.0D, 1024.0D)).setSyncable(true)); + public static final Holder SCALE = Attributes.register("scale", (new RangedAttribute("attribute.name.scale", 1.0D, 0.0625D, 16.0D)).setSyncable(true).setSentiment(Attribute.Sentiment.NEUTRAL)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch new file mode 100644 index 0000000000..64bab57963 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -70,6 +70,7 @@ + return false; + } else { + mutableLong.setValue(time + 20L + (long)world.getRandom().nextInt(20)); ++ if (entity.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck + PoiManager poiManager = world.getPoiManager(); + long2ObjectMap.long2ObjectEntrySet().removeIf(entry -> !entry.getValue().isStillValid(time)); + Predicate predicate2 = pos -> { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch new file mode 100644 index 0000000000..a3aec14777 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java ++++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java +@@ -9,6 +9,12 @@ + import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.npc.VillagerProfession; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftVillager; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.VillagerCareerChangeEvent; ++// CraftBukkit end ++ + public class AssignProfessionFromJobSite { + + public AssignProfessionFromJobSite() {} +@@ -37,7 +43,14 @@ + return villagerprofession.heldJobSite().test(holder); + }).findFirst(); + }).ifPresent((villagerprofession) -> { +- entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(villagerprofession)); ++ // CraftBukkit start - Fire VillagerCareerChangeEvent where Villager gets employed ++ VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(villagerprofession), VillagerCareerChangeEvent.ChangeReason.EMPLOYED); ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession()))); ++ // CraftBukkit end + entityvillager.refreshBrain(worldserver); + }); + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch new file mode 100644 index 0000000000..fe0de69d00 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java ++++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java +@@ -7,6 +7,12 @@ + import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; + import net.minecraft.world.entity.ai.memory.WalkTarget; ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++// CraftBukkit end + + public class BabyFollowAdult { + +@@ -25,9 +31,20 @@ + if (!entityageable.isBaby()) { + return false; + } else { +- AgeableMob entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor); ++ LivingEntity entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor); // CraftBukkit - type + + if (entityageable.closerThan(entityageable1, (double) (executionRange.getMaxValue() + 1)) && !entityageable.closerThan(entityageable1, (double) executionRange.getMinValue())) { ++ // CraftBukkit start ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityageable, entityageable1, EntityTargetEvent.TargetReason.FOLLOW_LEADER); ++ if (event.isCancelled()) { ++ return false; ++ } ++ if (event.getTarget() == null) { ++ memoryaccessor.erase(); ++ return true; ++ } ++ entityageable1 = ((CraftLivingEntity) event.getTarget()).getHandle(); ++ // CraftBukkit end + WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityageable1, false), (Float) speed.apply(entityageable), executionRange.getMinValue() - 1); + + memoryaccessor1.set(new EntityTracker(entityageable1, true)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch new file mode 100644 index 0000000000..dab10a1e20 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -14,6 +14,9 @@ + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // Paper start - configurable behavior tick rate and timings ++ private final String configKey; ++ // Paper end - configurable behavior tick rate and timings + + public Behavior(Map, MemoryStatus> requiredMemoryState) { + this(requiredMemoryState, 60); +@@ -27,6 +30,14 @@ + this.minDuration = minRunTime; + this.maxDuration = maxRunTime; + this.entryCondition = requiredMemoryState; ++ // Paper start - configurable behavior tick rate and timings ++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ // Paper end - configurable behavior tick rate and timings + } + + @Override +@@ -36,6 +47,12 @@ + + @Override + public final boolean tryStart(ServerLevel world, E entity, long time) { ++ // Paper start - configurable behavior tick rate and timings ++ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1); ++ if (tickRate > -1 && time < this.endTimestamp + tickRate) { ++ return false; ++ } ++ // Paper end - configurable behavior tick rate and timings + if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { + this.status = Behavior.Status.RUNNING; + int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch new file mode 100644 index 0000000000..35afe8f0a4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch @@ -0,0 +1,64 @@ +--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java ++++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java +@@ -60,7 +60,7 @@ + } + + public static void lookAtEntity(LivingEntity entity, LivingEntity target) { +- entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new EntityTracker(target, true))); ++ entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new EntityTracker(target, true))); // CraftBukkit - decompile error + } + + private static void setWalkAndLookTargetMemoriesToEachOther(LivingEntity first, LivingEntity second, float speed, int completionRange) { +@@ -79,8 +79,8 @@ + public static void setWalkAndLookTargetMemories(LivingEntity entity, PositionTracker target, float speed, int completionRange) { + WalkTarget memorytarget = new WalkTarget(target, speed, completionRange); + +- entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) target); +- entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) memorytarget); ++ entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, target); // CraftBukkit - decompile error ++ entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, memorytarget); // CraftBukkit - decompile error + } + + public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 targetLocation) { +@@ -90,6 +90,7 @@ + } + + public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 targetLocation, Vec3 velocityFactor, float yOffset) { ++ if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-4940: no empty loot + double d0 = entity.getEyeY() - (double) yOffset; + ItemEntity entityitem = new ItemEntity(entity.level(), entity.getX(), d0, entity.getZ(), stack); + +@@ -99,12 +100,19 @@ + vec3d2 = vec3d2.normalize().multiply(velocityFactor.x, velocityFactor.y, velocityFactor.z); + entityitem.setDeltaMovement(vec3d2); + entityitem.setDefaultPickUpDelay(); ++ // CraftBukkit start ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ entityitem.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + entity.level().addFreshEntity(entityitem); + } + + public static SectionPos findSectionClosestToVillage(ServerLevel world, SectionPos center, int radius) { + int j = world.sectionsToVillage(center); +- Stream stream = SectionPos.cube(center, radius).filter((sectionposition1) -> { ++ Stream stream = SectionPos.cube(center, radius).filter((sectionposition1) -> { // CraftBukkit - decompile error + return world.sectionsToVillage(sectionposition1) < j; + }); + +@@ -161,10 +169,10 @@ + + return optional.map((uuid) -> { + return ((ServerLevel) entity.level()).getEntity(uuid); +- }).map((entity) -> { ++ }).map((entity1) -> { // Paper - remap fix + LivingEntity entityliving1; + +- if (entity instanceof LivingEntity entityliving2) { ++ if (entity1 instanceof LivingEntity entityliving2) { // Paper - remap fix + entityliving1 = entityliving2; + } else { + entityliving1 = null; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch new file mode 100644 index 0000000000..fdb4490626 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -18,7 +18,7 @@ + private final Set> exitErasedMemories; + private final GateBehavior.OrderPolicy orderPolicy; + private final GateBehavior.RunningPolicy runningPolicy; +- private final ShufflingList> behaviors = new ShufflingList<>(); ++ private final ShufflingList> behaviors = new ShufflingList<>(false); // Paper - Fix Concurrency issue in ShufflingList during worldgen + private Behavior.Status status = Behavior.Status.STOPPED; + + public GateBehavior( diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch new file mode 100644 index 0000000000..d9692605cb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java ++++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java +@@ -28,6 +28,21 @@ + ItemEntity entityitem = (ItemEntity) behaviorbuilder_b.get(memoryaccessor2); + + if (behaviorbuilder_b.tryGet(memoryaccessor3).isEmpty() && startCondition.test(entityliving) && entityitem.closerThan(entityliving, (double) radius) && entityliving.level().getWorldBorder().isWithinBounds(entityitem.blockPosition()) && entityliving.canPickUpLoot()) { ++ // CraftBukkit start ++ if (entityliving instanceof net.minecraft.world.entity.animal.allay.Allay) { ++ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetEvent(entityliving, entityitem, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets ++ memoryaccessor2.erase(); ++ return false; // Paper - only erase allay memory on non-item targets ++ } ++ ++ entityitem = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle(); ++ } ++ // CraftBukkit end + WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityitem, false), speed, 0); + + memoryaccessor.set(new EntityTracker(entityitem, true)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch new file mode 100644 index 0000000000..066bfd1863 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch @@ -0,0 +1,63 @@ +--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java ++++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +@@ -22,10 +22,15 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.gameevent.GameEvent; ++ ++// CraftBukkit start ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.CropBlock; + import net.minecraft.world.level.block.FarmBlock; + import net.minecraft.world.level.block.state.BlockState; +-import net.minecraft.world.level.gameevent.GameEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class HarvestFarmland extends Behavior { + +@@ -82,8 +87,8 @@ + + protected void start(ServerLevel worldserver, Villager entityvillager, long i) { + if (i > this.nextOkStartTime && this.aboveFarmlandPos != null) { +- entityvillager.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BlockPosTracker(this.aboveFarmlandPos))); +- entityvillager.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); ++ entityvillager.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new BlockPosTracker(this.aboveFarmlandPos))); // CraftBukkit - decompile error ++ entityvillager.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); // CraftBukkit - decompile error + } + + } +@@ -103,7 +108,9 @@ + Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); + + if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state + world.destroyBlock(this.aboveFarmlandPos, true, entity); ++ } // CraftBukkit + } + + if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) { +@@ -120,9 +127,11 @@ + BlockItem itemblock = (BlockItem) item; + BlockState iblockdata1 = itemblock.getBlock().defaultBlockState(); + ++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata1)) { // CraftBukkit + world.setBlockAndUpdate(this.aboveFarmlandPos, iblockdata1); + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(entity, iblockdata1)); + flag = true; ++ } // CraftBukkit + } + } + +@@ -142,8 +151,8 @@ + this.aboveFarmlandPos = this.getValidFarmland(world); + if (this.aboveFarmlandPos != null) { + this.nextOkStartTime = time + 20L; +- entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); +- entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BlockPosTracker(this.aboveFarmlandPos))); ++ entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); // CraftBukkit - decompile error ++ entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new BlockPosTracker(this.aboveFarmlandPos))); // CraftBukkit - decompile error + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch new file mode 100644 index 0000000000..7348471be5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java ++++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +@@ -61,6 +61,13 @@ + DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); + + if (!blockdoor.isOpen(iblockdata)) { ++ // CraftBukkit start - entities opening doors ++ org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition)); ++ entityliving.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + blockdoor.setOpen(entityliving, worldserver, iblockdata, blockposition, true); + } + +@@ -76,6 +83,13 @@ + DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); + + if (!blockdoor1.isOpen(iblockdata1)) { ++ // CraftBukkit start - entities opening doors ++ org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition1)); ++ entityliving.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + blockdoor1.setOpen(entityliving, worldserver, iblockdata1, blockposition1, true); + optional = InteractWithDoor.rememberDoorToClose(memoryaccessor1, optional, worldserver, blockposition1); + } +@@ -129,7 +143,7 @@ + } + + private static boolean areOtherMobsComingThroughDoor(LivingEntity entity, BlockPos pos, Optional> otherMobs) { +- return otherMobs.isEmpty() ? false : ((List) otherMobs.get()).stream().filter((entityliving1) -> { ++ return otherMobs.isEmpty() ? false : (otherMobs.get()).stream().filter((entityliving1) -> { // CraftBukkit - decompile error + return entityliving1.getType() == entity.getType(); + }).filter((entityliving1) -> { + return pos.closerToCenterThan(entityliving1.position(), 2.0D); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch new file mode 100644 index 0000000000..18aef70f25 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch @@ -0,0 +1,73 @@ +--- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java ++++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java +@@ -13,6 +13,7 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundSource; + import net.minecraft.util.Mth; +@@ -30,6 +31,10 @@ + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public class PrepareRamNearestTarget extends Behavior { + +@@ -63,6 +68,13 @@ + return this.ramTargeting.test(worldserver, entitycreature, entityliving); + }); + }).ifPresent((entityliving) -> { ++ // CraftBukkit start ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entitycreature, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY); ++ if (event.isCancelled() || event.getTarget() == null) { ++ return; ++ } ++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle(); ++ // CraftBukkit end + this.chooseRamPosition(entitycreature, entityliving); + }); + } +@@ -72,7 +84,7 @@ + + if (!behaviorcontroller.hasMemoryValue(MemoryModuleType.RAM_TARGET)) { + world.broadcastEntityEvent(entity, (byte) 59); +- behaviorcontroller.setMemory(MemoryModuleType.RAM_COOLDOWN_TICKS, (Object) this.getCooldownOnFail.applyAsInt(entity)); ++ behaviorcontroller.setMemory(MemoryModuleType.RAM_COOLDOWN_TICKS, this.getCooldownOnFail.applyAsInt(entity)); // CraftBukkit - decompile error + } + + } +@@ -83,8 +95,8 @@ + + protected void tick(ServerLevel worldserver, E e0, long i) { + if (!this.ramCandidate.isEmpty()) { +- e0.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getStartPosition(), this.walkSpeed, 0))); +- e0.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new EntityTracker(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget(), true))); ++ e0.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getStartPosition(), this.walkSpeed, 0))); // CraftBukkit - decompile error ++ e0.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new EntityTracker(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget(), true))); // CraftBukkit - decompile error + boolean flag = !((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget().blockPosition().equals(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition()); + + if (flag) { +@@ -101,7 +113,7 @@ + } + + if (i - (Long) this.reachedRamPositionTimestamp.get() >= (long) this.ramPrepareTime) { +- e0.getBrain().setMemory(MemoryModuleType.RAM_TARGET, (Object) this.getEdgeOfBlock(blockposition, ((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition())); ++ e0.getBrain().setMemory(MemoryModuleType.RAM_TARGET, this.getEdgeOfBlock(blockposition, ((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition())); // CraftBukkit - decompile error + worldserver.playSound((Player) null, (Entity) e0, (SoundEvent) this.getPrepareRamSound.apply(e0), SoundSource.NEUTRAL, 1.0F, e0.getVoicePitch()); + this.ramCandidate = Optional.empty(); + } +@@ -153,7 +165,7 @@ + } + + PathNavigation navigationabstract = entity.getNavigation(); +- Stream stream = list.stream(); ++ Stream stream = list.stream(); // CraftBukkit - decompile error + BlockPos blockposition1 = entity.blockPosition(); + + Objects.requireNonNull(blockposition1); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch new file mode 100644 index 0000000000..3c23484d4f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/behavior/RamTarget.java ++++ b/net/minecraft/world/entity/ai/behavior/RamTarget.java +@@ -89,7 +89,7 @@ + float f = 0.25F * (float)(i - j); + float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f; + float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F; +- livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z()); ++ livingEntity.knockback(h * g * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.finishRam(world, entity); + world.playSound(null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F); + } else if (this.hasRammedHornBreakingBlock(world, entity)) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch new file mode 100644 index 0000000000..f5dc7ba366 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java ++++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java +@@ -6,6 +6,12 @@ + import net.minecraft.world.entity.npc.VillagerData; + import net.minecraft.world.entity.npc.VillagerProfession; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftVillager; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.VillagerCareerChangeEvent; ++// CraftBukkit end ++ + public class ResetProfession { + + public ResetProfession() {} +@@ -17,7 +23,14 @@ + VillagerData villagerdata = entityvillager.getVillagerData(); + + if (villagerdata.getProfession() != VillagerProfession.NONE && villagerdata.getProfession() != VillagerProfession.NITWIT && entityvillager.getVillagerXp() == 0 && villagerdata.getLevel() <= 1) { +- entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(VillagerProfession.NONE)); ++ // CraftBukkit start ++ VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(VillagerProfession.NONE), VillagerCareerChangeEvent.ChangeReason.LOSING_JOB); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession()))); ++ // CraftBukkit end + entityvillager.refreshBrain(worldserver); + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch new file mode 100644 index 0000000000..3f1d97092e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/entity/ai/behavior/ShufflingList.java ++++ b/net/minecraft/world/entity/ai/behavior/ShufflingList.java +@@ -16,12 +16,25 @@ + public class ShufflingList implements Iterable { + protected final List> entries; + private final RandomSource random = RandomSource.create(); ++ private final boolean isUnsafe; // Paper - Fix Concurrency issue in ShufflingList during worldgen + + public ShufflingList() { ++ // Paper start - Fix Concurrency issue in ShufflingList during worldgen ++ this(true); ++ } ++ public ShufflingList(boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end - Fix Concurrency issue in ShufflingList during worldgen + this.entries = Lists.newArrayList(); + } + + private ShufflingList(List> list) { ++ // Paper start - Fix Concurrency issue in ShufflingList during worldgen ++ this(list, true); ++ } ++ private ShufflingList(List> list, boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end - Fix Concurrency issue in ShufflingList during worldgen + this.entries = Lists.newArrayList(list); + } + +@@ -35,9 +48,12 @@ + } + + public ShufflingList shuffle() { +- this.entries.forEach(entry -> entry.setRandom(this.random.nextFloat())); +- this.entries.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); +- return this; ++ // Paper start - Fix Concurrency issue in ShufflingList during worldgen ++ List> list = this.isUnsafe ? Lists.newArrayList(this.entries) : this.entries; ++ list.forEach(entry -> entry.setRandom(this.random.nextFloat())); ++ list.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); ++ return this.isUnsafe ? new ShufflingList<>(list, this.isUnsafe) : this; ++ // Paper end - Fix Concurrency issue in ShufflingList during worldgen + } + + public Stream stream() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch new file mode 100644 index 0000000000..d905f5c1d7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/entity/ai/behavior/SleepInBed.java ++++ b/net/minecraft/world/entity/ai/behavior/SleepInBed.java +@@ -42,7 +42,8 @@ + } + } + +- BlockState blockState = world.getBlockState(globalPos.pos()); ++ BlockState blockState = world.getBlockStateIfLoaded(globalPos.pos()); // Paper - Prevent sync chunk loads when villagers try to find beds ++ if (blockState == null) { return false; } // Paper - Prevent sync chunk loads when villagers try to find beds + return globalPos.pos().closerToCenterThan(entity.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch new file mode 100644 index 0000000000..0a02e4eb2c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java ++++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java +@@ -2,10 +2,15 @@ + + import java.util.Optional; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public class StartAttacking { + +@@ -34,6 +39,17 @@ + if (!entityinsentient.canAttack(entityliving)) { + return false; + } else { ++ // CraftBukkit start ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY); ++ if (event.isCancelled()) { ++ return false; ++ } ++ if (event.getTarget() == null) { ++ memoryaccessor.erase(); ++ return true; ++ } ++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle(); ++ // CraftBukkit end + memoryaccessor.set(entityliving); + memoryaccessor1.erase(); + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch new file mode 100644 index 0000000000..c156a35921 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java ++++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +@@ -7,6 +7,12 @@ + import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder; + import net.minecraft.world.entity.ai.memory.MemoryModuleType; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end ++ + public class StopAttackingIfTargetInvalid { + + private static final int TIMEOUT_TO_GET_WITHIN_ATTACK_RANGE = 200; +@@ -40,6 +46,30 @@ + if (entityinsentient.canAttack(entityliving) && (!shouldForgetIfTargetUnreachable || !StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) && entityliving.isAlive() && entityliving.level() == entityinsentient.level() && !condition.test(worldserver, entityliving)) { + return true; + } else { ++ // Paper start - better track target change reason ++ final EntityTargetEvent.TargetReason reason; ++ if (!entityinsentient.canAttack(entityliving)) { ++ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; ++ } else if (shouldForgetIfTargetUnreachable && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) { ++ reason = EntityTargetEvent.TargetReason.FORGOT_TARGET; ++ } else if (!entityliving.isAlive()) { ++ reason = EntityTargetEvent.TargetReason.TARGET_DIED; ++ } else if (entityliving.level() != entityinsentient.level()) { ++ reason = EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL; ++ } else { ++ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; ++ } ++ // Paper end ++ // CraftBukkit start ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, reason); // Paper ++ if (event.isCancelled()) { ++ return false; ++ } ++ if (event.getTarget() != null) { ++ entityinsentient.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); ++ return true; ++ } ++ // CraftBukkit end + callback.accept(worldserver, entityinsentient, entityliving); + memoryaccessor.erase(); + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch new file mode 100644 index 0000000000..e0fa77caef --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java ++++ b/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java +@@ -39,6 +39,12 @@ + if (worldserver.getBlockState(blockposition2).isAir()) { + BlockState iblockdata = frogSpawn.defaultBlockState(); + ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityliving, blockposition2, iblockdata)) { ++ memoryaccessor2.erase(); ++ return true; ++ } ++ // CraftBukkit end + worldserver.setBlock(blockposition2, iblockdata, 3); + worldserver.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition2, GameEvent.Context.of(entityliving, iblockdata)); + worldserver.playSound((Player) null, (Entity) entityliving, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch new file mode 100644 index 0000000000..c8260c2305 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java ++++ b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +@@ -42,7 +42,7 @@ + Pair.of(1, new MoveToTargetSink()), + Pair.of(2, PoiCompetitorScan.create()), + Pair.of(3, new LookAndFollowTradingPlayerSink(speed)), +- Pair.of(5, GoToWantedItem.create(speed, false, 4)), ++ Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speed, false, 4)), // Paper - Fix MC-157464 + Pair.of( + 6, + AcquirePoi.create( diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch new file mode 100644 index 0000000000..9875c853b9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java ++++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java +@@ -17,6 +17,10 @@ + import net.minecraft.world.entity.ai.village.poi.PoiTypes; + import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.level.pathfinder.Path; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++// CraftBukkit end + + public class VillagerMakeLove extends Behavior { + +@@ -114,11 +118,17 @@ + if (entityvillager2 == null) { + return Optional.empty(); + } else { +- parent.setAge(6000); +- partner.setAge(6000); + entityvillager2.setAge(-24000); + entityvillager2.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F); +- world.addFreshEntityWithPassengers(entityvillager2); ++ // CraftBukkit start - call EntityBreedEvent ++ if (CraftEventFactory.callEntityBreedEvent(entityvillager2, parent, partner, null, null, 0).isCancelled()) { ++ return Optional.empty(); ++ } ++ // Move age setting down ++ parent.setAge(6000); ++ partner.setAge(6000); ++ world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING); ++ // CraftBukkit end + world.broadcastEntityEvent(entityvillager2, (byte) 12); + return Optional.of(entityvillager2); + } +@@ -127,6 +137,6 @@ + private void giveBedToChild(ServerLevel world, Villager child, BlockPos pos) { + GlobalPos globalpos = GlobalPos.of(world.dimension(), pos); + +- child.getBrain().setMemory(MemoryModuleType.HOME, (Object) globalpos); ++ child.getBrain().setMemory(MemoryModuleType.HOME, globalpos); // CraftBukkit - decompile error + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch new file mode 100644 index 0000000000..3d8d9ce4bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java ++++ b/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java +@@ -86,7 +86,9 @@ + simpleContainer.removeItemType(Items.WHEAT, m); + ItemStack itemStack = simpleContainer.addItem(new ItemStack(Items.BREAD, l)); + if (!itemStack.isEmpty()) { ++ villager.forceDrops = true; // Paper - Add missing forceDrop toggles + villager.spawnAtLocation(world, itemStack, 0.5F); ++ villager.forceDrops = false; // Paper - Add missing forceDrop toggles + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch new file mode 100644 index 0000000000..6f3b4a357c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/entity/ai/behavior/warden/Digging.java ++++ b/net/minecraft/world/entity/ai/behavior/warden/Digging.java +@@ -10,6 +10,10 @@ + import net.minecraft.world.entity.ai.memory.MemoryStatus; + import net.minecraft.world.entity.monster.warden.Warden; + ++// CraftBukkit start - imports ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end ++ + public class Digging extends Behavior { + + public Digging(int duration) { +@@ -37,7 +41,7 @@ + + protected void stop(ServerLevel worldserver, E e0, long i) { + if (e0.getRemovalReason() == null) { +- e0.remove(Entity.RemovalReason.DISCARDED); ++ e0.remove(Entity.RemovalReason.DISCARDED, EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch new file mode 100644 index 0000000000..4fbd06f256 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java ++++ b/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java +@@ -83,7 +83,7 @@ + if (target.hurtServer(world, world.damageSources().sonicBoom(entity), 10.0F)) { + double d = 0.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); + double e = 2.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); +- target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e); ++ target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + }); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch new file mode 100644 index 0000000000..8323bfa7ee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -72,9 +72,16 @@ + } + + if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state ++ this.start(); ++ return; ++ } ++ // CraftBukkit end ++ final net.minecraft.world.level.block.state.BlockState oldState = this.mob.level().getBlockState(this.doorPos); // Paper - fix MC-263999 + this.mob.level().removeBlock(this.doorPos, false); + this.mob.level().levelEvent(1021, this.doorPos, 0); +- this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos))); ++ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999 + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch new file mode 100644 index 0000000000..72de1e0703 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -11,6 +11,10 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.predicate.BlockStatePredicate; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end ++ + public class EatBlockGoal extends Goal { + + private static final int EAT_ANIMATION_TICKS = 40; +@@ -27,6 +31,11 @@ + + @Override + public boolean canUse() { ++ // Paper start - Fix MC-210802 ++ if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) { ++ return false; ++ } ++ // Paper end + if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { + return false; + } else { +@@ -63,8 +72,9 @@ + if (this.eatAnimationTick == this.adjustedTickDelay(4)) { + BlockPos blockposition = this.mob.blockPosition(); + +- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) { +- if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state ++ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state + this.level.destroyBlock(blockposition, false); + } + +@@ -73,7 +83,7 @@ + BlockPos blockposition1 = blockposition.below(); + + if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { +- if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state + this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); + this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch new file mode 100644 index 0000000000..ed90ccea87 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/entity/ai/goal/FloatGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FloatGoal.java +@@ -9,6 +9,7 @@ + + public FloatGoal(Mob mob) { + this.mob = mob; ++ if (mob.getCommandSenderWorld().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float + this.setFlags(EnumSet.of(Goal.Flag.JUMP)); + mob.getNavigation().setCanFloat(true); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch new file mode 100644 index 0000000000..4cdf53ef91 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +@@ -72,7 +72,7 @@ + public void tick() { + boolean bl = this.tamable.shouldTryTeleportToOwner(); + if (!bl) { +- this.tamable.getLookControl().setLookAt(this.owner, 10.0F, (float)this.tamable.getMaxHeadXRot()); ++ if (this.tamable.distanceToSqr(this.owner) <= 16 * 16) this.tamable.getLookControl().setLookAt(this.owner, 10.0F, (float)this.tamable.getMaxHeadXRot()); // Paper - Limit pet look distance + } + + if (--this.timeToRecalcPath <= 0) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch new file mode 100644 index 0000000000..a354e63b19 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/net/minecraft/world/entity/ai/goal/Goal.java +@@ -46,6 +46,16 @@ + return this.flags; + } + ++ // Paper start - Mob Goal API ++ public boolean hasFlag(final Goal.Flag flag) { ++ return this.flags.contains(flag); ++ } ++ ++ public void addFlag(final Goal.Flag flag) { ++ this.flags.add(flag); ++ } ++ // Paper end - Mob Goal API ++ + protected int adjustedTickDelay(int ticks) { + return this.requiresUpdateEveryTick() ? ticks : reducedTickDelay(ticks); + } +@@ -62,7 +72,19 @@ + return (ServerLevel)world; + } + ++ // Paper start - Mob goal api ++ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; ++ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { ++ if(this.vanillaGoal == null) { ++ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); ++ } ++ //noinspection unchecked ++ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; ++ } ++ // Paper end - Mob goal api ++ + public static enum Flag { ++ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR + MOVE, + LOOK, + JUMP, diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch new file mode 100644 index 0000000000..2111952674 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +@@ -21,6 +21,10 @@ + import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class RemoveBlockGoal extends MoveToBlockGoal { + +@@ -97,6 +101,11 @@ + } + + if (this.ticksSinceReachedGoal > 60) { ++ // CraftBukkit start - Step on eggs ++ if (!CraftEventFactory.callEntityInteractEvent(this.removerMob, CraftBlock.at(world, blockposition1))) { ++ return; ++ } ++ // CraftBukkit end + world.removeBlock(blockposition1, false); + if (!world.isClientSide) { + for (int i = 0; i < 20; ++i) { +@@ -118,7 +127,9 @@ + + @Nullable + private BlockPos getPosWithBlock(BlockPos pos, BlockGetter world) { +- if (world.getBlockState(pos).is(this.blockToRemove)) { ++ net.minecraft.world.level.block.state.BlockState block = world.getBlockStateIfLoaded(pos); // Paper - Prevent AI rules from loading chunks ++ if (block == null) return null; // Paper - Prevent AI rules from loading chunks ++ if (block.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks + return pos; + } else { + BlockPos[] ablockposition = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()}; +@@ -128,7 +139,8 @@ + for (int j = 0; j < i; ++j) { + BlockPos blockposition1 = ablockposition1[j]; + +- if (world.getBlockState(blockposition1).is(this.blockToRemove)) { ++ net.minecraft.world.level.block.state.BlockState block2 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent AI rules from loading chunks ++ if (block2 != null && block2.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks + return blockposition1; + } + } +@@ -139,7 +151,7 @@ + + @Override + protected boolean isValidTarget(LevelReader world, BlockPos pos) { +- ChunkAccess ichunkaccess = world.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); ++ ChunkAccess ichunkaccess = world.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks + + return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).is(this.blockToRemove) && ichunkaccess.getBlockState(pos.above()).isAir() && ichunkaccess.getBlockState(pos.above(2)).isAir(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch new file mode 100644 index 0000000000..4f41b508ea --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java ++++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +@@ -6,6 +6,10 @@ + import net.minecraft.world.entity.animal.horse.AbstractHorse; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class RunAroundLikeCrazyGoal extends Goal { + +@@ -63,7 +67,7 @@ + int i = this.horse.getTemper(); + int j = this.horse.getMaxTemper(); + +- if (j > 0 && this.horse.getRandom().nextInt(j) < i) { ++ if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent + this.horse.tameWithName(entityhuman); + return; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch new file mode 100644 index 0000000000..64830b4a5b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java ++++ b/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java +@@ -22,7 +22,7 @@ + @Override + public boolean canUse() { + if (!this.mob.isTame()) { +- return false; ++ return this.mob.isOrderedToSit() && this.mob.getTarget() == null; // CraftBukkit - Allow sitting for wild animals + } else if (this.mob.isInWaterOrBubble()) { + return false; + } else if (!this.mob.onGround()) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch new file mode 100644 index 0000000000..885e0fd98c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java ++++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java +@@ -21,7 +21,14 @@ + return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0; + } + ++ // Paper start - Fix MC-179072 + @Override ++ public boolean canContinueToUse() { ++ return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse(); ++ } ++ // Paper end ++ ++ @Override + public void start() { + this.creeper.getNavigation().stop(); + this.target = this.creeper.getTarget(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch new file mode 100644 index 0000000000..8d33e61ef6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -8,9 +8,15 @@ + import net.minecraft.world.entity.PathfinderMob; + import net.minecraft.world.entity.ai.attributes.Attributes; + import net.minecraft.world.entity.ai.targeting.TargetingConditions; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++// CraftBukkit end ++ + public class TemptGoal extends Goal { + + private static final TargetingConditions TEMPT_TARGETING = TargetingConditions.forNonCombat().ignoreLineOfSight(); +@@ -23,7 +29,7 @@ + private double pRotX; + private double pRotY; + @Nullable +- protected Player player; ++ protected LivingEntity player; // CraftBukkit + private int calmDown; + private boolean isRunning; + private final Predicate items; +@@ -47,6 +53,15 @@ + return false; + } else { + this.player = getServerLevel((Entity) this.mob).getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ // CraftBukkit start ++ if (this.player != null) { ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.player = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ // CraftBukkit end + return this.player != null; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch new file mode 100644 index 0000000000..3a2b688e7b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +@@ -61,7 +61,7 @@ + + @Override + public void start() { +- this.golem.setTarget(this.potentialTarget); ++ this.golem.setTarget(this.potentialTarget, org.bukkit.event.entity.EntityTargetEvent.TargetReason.DEFEND_VILLAGE, true); // CraftBukkit - reason + super.start(); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch new file mode 100644 index 0000000000..3b4092a6f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +@@ -67,7 +67,7 @@ + + @Override + public void start() { +- this.mob.setTarget(this.mob.getLastHurtByMob()); ++ this.mob.setTarget(this.mob.getLastHurtByMob(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit - reason + this.targetMob = this.mob.getTarget(); + this.timestamp = this.mob.getLastHurtByMobTimestamp(); + this.unseenMemoryTicks = 300; +@@ -114,6 +114,6 @@ + } + + protected void alertOther(Mob mob, LivingEntity target) { +- mob.setTarget(target); ++ mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch new file mode 100644 index 0000000000..5d304f4ac8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +@@ -70,7 +70,7 @@ + + @Override + public void start() { +- this.mob.setTarget(this.target); ++ this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason + super.start(); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch new file mode 100644 index 0000000000..edaee8e182 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java +@@ -38,7 +38,7 @@ + + @Override + public void start() { +- this.mob.setTarget(this.ownerLastHurtBy); ++ this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit - reason + LivingEntity entityliving = this.tameAnimal.getOwner(); + + if (entityliving != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch new file mode 100644 index 0000000000..696e8ea9fa --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java +@@ -38,7 +38,7 @@ + + @Override + public void start() { +- this.mob.setTarget(this.ownerLastHurt); ++ this.mob.setTarget(this.ownerLastHurt, org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit - reason + LivingEntity entityliving = this.tameAnimal.getOwner(); + + if (entityliving != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch new file mode 100644 index 0000000000..ab51c6b369 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/entity/ai/goal/target/TargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/TargetGoal.java +@@ -10,6 +10,9 @@ + import net.minecraft.world.level.pathfinder.Node; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.scores.PlayerTeam; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public abstract class TargetGoal extends Goal { + +@@ -69,7 +72,7 @@ + } + } + +- this.mob.setTarget(entityliving); ++ this.mob.setTarget(entityliving, EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit + return true; + } + } +@@ -89,7 +92,7 @@ + + @Override + public void stop() { +- this.mob.setTarget((LivingEntity) null); ++ this.mob.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit + this.targetMob = null; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch new file mode 100644 index 0000000000..55d0a4951d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -216,6 +216,43 @@ + public void remove(GossipType gossipType) { + this.entries.removeInt(gossipType); + } ++ ++ // Paper start - Add villager reputation API ++ private static final GossipType[] TYPES = GossipType.values(); ++ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() { ++ Map map = new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class); ++ for (Object2IntMap.Entry type : this.entries.object2IntEntrySet()) { ++ map.put(toApi(type.getKey()), type.getIntValue()); ++ } ++ ++ return new com.destroystokyo.paper.entity.villager.Reputation(map); ++ } ++ ++ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) { ++ for (GossipType type : TYPES) { ++ com.destroystokyo.paper.entity.villager.ReputationType api = toApi(type); ++ ++ if (rep.hasReputationSet(api)) { ++ int reputation = rep.getReputation(api); ++ if (reputation == 0) { ++ this.entries.removeInt(type); ++ } else { ++ this.entries.put(type, reputation); ++ } ++ } ++ } ++ } ++ ++ private static com.destroystokyo.paper.entity.villager.ReputationType toApi(GossipType type) { ++ return switch (type) { ++ case MAJOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE; ++ case MINOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE; ++ case MINOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE; ++ case MAJOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE; ++ case TRADING -> com.destroystokyo.paper.entity.villager.ReputationType.TRADING; ++ }; ++ } ++ // Paper end - Add villager reputation API + } + + static record GossipEntry(UUID target, GossipType type, int value) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch new file mode 100644 index 0000000000..ac1686a6c3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +@@ -39,7 +39,7 @@ + + @Override + public Path createPath(Entity entity, int distance) { +- return this.createPath(entity.blockPosition(), distance); ++ return this.createPath(entity.blockPosition(), entity, distance); // Paper - EntityPathfindEvent + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch new file mode 100644 index 0000000000..36cc381dc3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +@@ -41,7 +41,7 @@ + } + + @Override +- public Path createPath(BlockPos target, int distance) { ++ public Path createPath(BlockPos target, @javax.annotation.Nullable Entity entity, int distance) { // Paper - EntityPathfindEvent + LevelChunk levelChunk = this.level + .getChunkSource() + .getChunkNow(SectionPos.blockToSectionCoord(target.getX()), SectionPos.blockToSectionCoord(target.getZ())); +@@ -56,7 +56,7 @@ + } + + if (mutableBlockPos.getY() > this.level.getMinY()) { +- return super.createPath(mutableBlockPos.above(), distance); ++ return super.createPath(mutableBlockPos.above(), entity, distance); // Paper - EntityPathfindEvent + } + + mutableBlockPos.setY(target.getY() + 1); +@@ -69,7 +69,7 @@ + } + + if (!levelChunk.getBlockState(target).isSolid()) { +- return super.createPath(target, distance); ++ return super.createPath(target, entity, distance); // Paper - EntityPathfindEvent + } else { + BlockPos.MutableBlockPos mutableBlockPos2 = target.mutable().move(Direction.UP); + +@@ -77,14 +77,14 @@ + mutableBlockPos2.move(Direction.UP); + } + +- return super.createPath(mutableBlockPos2.immutable(), distance); ++ return super.createPath(mutableBlockPos2.immutable(), entity, distance); // Paper - EntityPathfindEvent + } + } + } + + @Override + public Path createPath(Entity entity, int distance) { +- return this.createPath(entity.blockPosition(), distance); ++ return this.createPath(entity.blockPosition(), entity, distance); // Paper - EntityPathfindEvent + } + + private int getSurfaceY() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch new file mode 100644 index 0000000000..124ed48519 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch @@ -0,0 +1,104 @@ +--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -125,8 +125,14 @@ + + @Nullable + public Path createPath(BlockPos target, int distance) { +- return this.createPath(ImmutableSet.of(target), 8, false, distance); ++ // Paper start - EntityPathfindEvent ++ return this.createPath(target, null, distance); + } ++ @Nullable ++ public Path createPath(BlockPos target, @Nullable Entity entity, int distance) { ++ return this.createPath(ImmutableSet.of(target), entity, 8, false, distance); ++ // Paper end - EntityPathfindEvent ++ } + + @Nullable + public Path createPath(BlockPos target, int minDistance, int maxDistance) { +@@ -135,7 +141,7 @@ + + @Nullable + public Path createPath(Entity entity, int distance) { +- return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, distance); ++ return this.createPath(ImmutableSet.of(entity.blockPosition()), entity, 16, true, distance); // Paper - EntityPathfindEvent + } + + @Nullable +@@ -145,6 +151,17 @@ + + @Nullable + protected Path createPath(Set positions, int range, boolean useHeadPos, int distance, float followRange) { ++ // Paper start - EntityPathfindEvent ++ return this.createPath(positions, null, range, useHeadPos, distance, followRange); ++ } ++ ++ @Nullable ++ protected Path createPath(Set positions, @Nullable Entity target, int range, boolean useHeadPos, int distance) { ++ return this.createPath(positions, target, range, useHeadPos, distance, (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE)); ++ } ++ ++ @Nullable protected Path createPath(Set positions, @Nullable Entity target, int range, boolean useHeadPos, int distance, float followRange) { ++ // Paper end - EntityPathfindEvent + if (positions.isEmpty()) { + return null; + } else if (this.mob.getY() < (double)this.level.getMinY()) { +@@ -154,6 +171,23 @@ + } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) { + return this.path; + } else { ++ // Paper start - EntityPathfindEvent ++ boolean copiedSet = false; ++ for (BlockPos possibleTarget : positions) { ++ if (!this.mob.getCommandSenderWorld().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border ++ io.papermc.paper.util.MCUtil.toLocation(this.mob.level(), possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) { ++ if (!copiedSet) { ++ copiedSet = true; ++ positions = new java.util.HashSet<>(positions); ++ } ++ // note: since we copy the set this remove call is safe, since we're iterating over the old copy ++ positions.remove(possibleTarget); ++ if (positions.isEmpty()) { ++ return null; ++ } ++ } ++ } ++ // Paper end - EntityPathfindEvent + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("pathfind"); + BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition(); +@@ -175,13 +209,33 @@ + return this.moveTo(this.createPath(x, y, z, 1), speed); + } + ++ // Paper start - Perf: Optimise pathfinding ++ private int lastFailure = 0; ++ private int pathfindFailures = 0; ++ // Paper end - Perf: Optimise pathfinding ++ + public boolean moveTo(double x, double y, double z, int distance, double speed) { + return this.moveTo(this.createPath(x, y, z, distance), speed); + } + + public boolean moveTo(Entity entity, double speed) { ++ // Paper start - Perf: Optimise pathfinding ++ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { ++ return false; ++ } ++ // Paper end - Perf: Optimise pathfinding + Path path = this.createPath(entity, 1); +- return path != null && this.moveTo(path, speed); ++ // Paper start - Perf: Optimise pathfinding ++ if (path != null && this.moveTo(path, speed)) { ++ this.lastFailure = 0; ++ this.pathfindFailures = 0; ++ return true; ++ } else { ++ this.pathfindFailures++; ++ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; ++ return false; ++ } ++ // Paper end - Perf: Optimise pathfinding + } + + public boolean moveTo(@Nullable Path path, double speed) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch new file mode 100644 index 0000000000..b2662d7401 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java +@@ -16,9 +16,9 @@ + } + + @Override +- public Path createPath(BlockPos target, int distance) { ++ public Path createPath(BlockPos target, @Nullable Entity entity, int distance) { // Paper - EntityPathfindEvent + this.pathToPosition = target; +- return super.createPath(target, distance); ++ return super.createPath(target, entity, distance); // Paper - EntityPathfindEvent + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch new file mode 100644 index 0000000000..7d220911a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/entity/ai/sensing/Sensor.java ++++ b/net/minecraft/world/entity/ai/sensing/Sensor.java +@@ -29,8 +29,19 @@ + .ignoreInvisibilityTesting(); + private final int scanRate; + private long timeToTick; ++ // Paper start - configurable sensor tick rate and timings ++ private final String configKey; ++ // Paper end + + public Sensor(int senseInterval) { ++ // Paper start - configurable sensor tick rate and timings ++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ // Paper end + this.scanRate = senseInterval; + this.timeToTick = (long)RANDOM.nextInt(senseInterval); + } +@@ -41,8 +52,10 @@ + + public final void tick(ServerLevel world, E entity) { + if (--this.timeToTick <= 0L) { +- this.timeToTick = (long)this.scanRate; ++ // Paper start - configurable sensor tick rate and timings ++ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); + this.updateTargetingConditionRanges(entity); ++ // Paper end + this.doTick(world, entity); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch new file mode 100644 index 0000000000..2bd53d40cd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +@@ -19,6 +19,14 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTargetLivingEntityEvent; ++// CraftBukkit end ++ + public class TemptingSensor extends Sensor { + + private static final TargetingConditions TEMPT_TARGETING = TargetingConditions.forNonCombat().ignoreLineOfSight(); +@@ -31,7 +39,7 @@ + protected void doTick(ServerLevel world, PathfinderMob entity) { + Brain behaviorcontroller = entity.getBrain(); + TargetingConditions pathfindertargetcondition = TemptingSensor.TEMPT_TARGETING.copy().range((double) ((float) entity.getAttributeValue(Attributes.TEMPT_RANGE))); +- Stream stream = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((entityplayer) -> { ++ Stream stream = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((entityplayer) -> { // CraftBukkit - decompile error + return pathfindertargetcondition.test(world, entity, entityplayer); + }).filter(this::playerHoldingTemptation).filter((entityplayer) -> { + return !entity.hasPassenger((Entity) entityplayer); +@@ -43,7 +51,17 @@ + if (!list.isEmpty()) { + Player entityhuman = (Player) list.get(0); + +- behaviorcontroller.setMemory(MemoryModuleType.TEMPTING_PLAYER, (Object) entityhuman); ++ // CraftBukkit start ++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, entityhuman, EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return; ++ } ++ if (event.getTarget() instanceof HumanEntity) { ++ behaviorcontroller.setMemory(MemoryModuleType.TEMPTING_PLAYER, ((CraftHumanEntity) event.getTarget()).getHandle()); ++ } else { ++ behaviorcontroller.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); ++ } ++ // CraftBukkit end + } else { + behaviorcontroller.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch new file mode 100644 index 0000000000..4b4a523928 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/entity/ai/village/VillageSiege.java ++++ b/net/minecraft/world/entity/ai/village/VillageSiege.java +@@ -117,11 +117,12 @@ + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.EVENT, (SpawnGroupData) null); + } catch (Exception exception) { + VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + return; + } + + entityzombie.moveTo(vec3d.x, vec3d.y, vec3d.z, world.random.nextFloat() * 360.0F, 0.0F); +- world.addFreshEntityWithPassengers(entityzombie); ++ world.addFreshEntityWithPassengers(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch new file mode 100644 index 0000000000..acfaa378fe --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/world/entity/ambient/Bat.java ++++ b/net/minecraft/world/entity/ambient/Bat.java +@@ -29,6 +29,9 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.levelgen.Heightmap; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class Bat extends AmbientCreature { + +@@ -88,7 +91,7 @@ + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return false; + } + +@@ -144,13 +147,13 @@ + this.yHeadRot = (float) this.random.nextInt(360); + } + +- if (world.getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null) { ++ if (world.getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent + this.setResting(false); + if (!flag) { + world.levelEvent((Player) null, 1025, blockposition, 0); + } + } +- } else { ++ } else if (CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent + this.setResting(false); + if (!flag) { + world.levelEvent((Player) null, 1025, blockposition, 0); +@@ -177,7 +180,7 @@ + + this.zza = 0.5F; + this.setYRot(this.getYRot() + f1); +- if (this.random.nextInt(100) == 0 && world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1)) { ++ if (this.random.nextInt(100) == 0 && world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1) && CraftEventFactory.handleBatToggleSleepEvent(this, false)) { // CraftBukkit - Call BatToggleSleepEvent + this.setResting(true); + } + } +@@ -202,7 +205,7 @@ + if (this.isInvulnerableTo(world, source)) { + return false; + } else { +- if (this.isResting()) { ++ if (this.isResting() && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent + this.setResting(false); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch new file mode 100644 index 0000000000..69bd0b2aef --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/entity/animal/AbstractSchoolingFish.java ++++ b/net/minecraft/world/entity/animal/AbstractSchoolingFish.java +@@ -51,6 +51,7 @@ + } + + public void stopFollowing() { ++ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called + this.leader.removeFollower(); + this.leader = null; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch new file mode 100644 index 0000000000..f8c533216c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch @@ -0,0 +1,141 @@ +--- a/net/minecraft/world/entity/animal/Animal.java ++++ b/net/minecraft/world/entity/animal/Animal.java +@@ -35,12 +35,20 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.pathfinder.PathType; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityBreedEvent; ++import org.bukkit.event.entity.EntityDamageEvent; ++import org.bukkit.event.entity.EntityEnterLoveModeEvent; ++// CraftBukkit end ++ + public abstract class Animal extends AgeableMob { + + protected static final int PARENT_AGE_AFTER_BREEDING = 6000; + public int inLove; + @Nullable + public UUID loveCause; ++ public ItemStack breedItem; // CraftBukkit - Add breedItem variable + + protected Animal(EntityType type, Level world) { + super(type, world); +@@ -82,9 +90,15 @@ + } + + @Override +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { ++ // CraftBukkit start - void -> boolean ++ public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { ++ boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event); ++ if (!damageResult) { ++ return false; ++ } + this.resetLove(); +- super.actuallyHurt(world, source, amount); ++ return true; ++ // CraftBukkit end + } + + @Override +@@ -144,8 +158,9 @@ + int i = this.getAge(); + + if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + this.playEatingSound(); + return InteractionResult.SUCCESS_SERVER; + } +@@ -187,11 +202,26 @@ + return this.inLove <= 0; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying + public void setInLove(@Nullable Player player) { +- this.inLove = 600; ++ // Paper start - Fix EntityBreedEvent copying ++ this.setInLove(player, null); ++ } ++ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { ++ if (breedItemCopy != null) this.breedItem = breedItemCopy; ++ // Paper end - Fix EntityBreedEvent copying ++ // CraftBukkit start ++ EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600); ++ if (entityEnterLoveModeEvent.isCancelled()) { ++ this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled ++ return; ++ } ++ this.inLove = entityEnterLoveModeEvent.getTicksInLove(); ++ // CraftBukkit end + if (player != null) { + this.loveCause = player.getUUID(); + } ++ // Paper - Fix EntityBreedEvent copying; set breed item in better place + + this.level().broadcastEntityEvent(this, (byte) 18); + } +@@ -233,25 +263,48 @@ + if (entityageable != null) { + entityageable.setBaby(true); + entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); +- this.finalizeSpawnChildFromBreeding(world, other, entityageable); +- world.addFreshEntityWithPassengers(entityageable); ++ // CraftBukkit start - call EntityBreedEvent ++ ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> { ++ return Optional.ofNullable(other.getLoveCause()); ++ }).orElse(null); ++ int experience = this.getRandom().nextInt(7) + 1; ++ EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience); ++ if (entityBreedEvent.isCancelled()) { ++ return; ++ } ++ experience = entityBreedEvent.getExperience(); ++ this.finalizeSpawnChildFromBreeding(world, other, entityageable, experience); ++ world.addFreshEntityWithPassengers(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); ++ // CraftBukkit end + } + } + + public void finalizeSpawnChildFromBreeding(ServerLevel world, Animal other, @Nullable AgeableMob baby) { +- Optional.ofNullable(this.getLoveCause()).or(() -> { +- return Optional.ofNullable(other.getLoveCause()); +- }).ifPresent((entityplayer) -> { ++ // CraftBukkit start ++ this.finalizeSpawnChildFromBreeding(world, other, baby, this.getRandom().nextInt(7) + 1); ++ } ++ ++ public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) { ++ // CraftBukkit end ++ // Paper start ++ ServerPlayer entityplayer = this.getLoveCause(); ++ if (entityplayer == null) entityplayer = entityanimal.getLoveCause(); ++ if (entityplayer != null) { ++ // Paper end + entityplayer.awardStat(Stats.ANIMALS_BRED); +- CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, other, baby); +- }); ++ CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); ++ } // Paper + this.setAge(6000); +- other.setAge(6000); ++ entityanimal.setAge(6000); + this.resetLove(); +- other.resetLove(); +- world.broadcastEntityEvent(this, (byte) 18); +- if (world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1)); ++ entityanimal.resetLove(); ++ worldserver.broadcastEntityEvent(this, (byte) 18); ++ if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ // CraftBukkit start - use event experience ++ if (experience > 0) { ++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper ++ } ++ // CraftBukkit end + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch new file mode 100644 index 0000000000..748c94de8e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch @@ -0,0 +1,205 @@ +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -92,6 +92,11 @@ + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public class Bee extends Animal implements NeutralMob, FlyingAnimal { + +@@ -149,7 +154,22 @@ + public Bee(EntityType type, Level world) { + super(type, world); + this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); +- this.moveControl = new FlyingMoveControl(this, 20, true); ++ // Paper start - Fix MC-167279 ++ class BeeFlyingMoveControl extends FlyingMoveControl { ++ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { ++ super(entity, maxPitchChange, noGravity); ++ } ++ ++ @Override ++ public void tick() { ++ if (this.mob.getY() <= Bee.this.level().getMinY()) { ++ this.mob.setNoGravity(false); ++ } ++ super.tick(); ++ } ++ } ++ this.moveControl = new BeeFlyingMoveControl(this, 20, true); ++ // Paper end - Fix MC-167279 + this.lookControl = new Bee.BeeLookControl(this); + this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); + this.setPathfindingMalus(PathType.WATER, -1.0F); +@@ -198,21 +218,28 @@ + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { +- super.addAdditionalSaveData(nbt); +- if (this.hasHive()) { +- nbt.put("hive_pos", NbtUtils.writeBlockPos(this.getHivePos())); ++ // CraftBukkit start - selectively save data ++ this.addAdditionalSaveData(nbt, true); ++ } ++ ++ @Override ++ public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) { ++ // CraftBukkit end ++ super.addAdditionalSaveData(nbttagcompound); ++ if (includeAll && this.hasHive()) { // CraftBukkit - selectively save hive ++ nbttagcompound.put("hive_pos", NbtUtils.writeBlockPos(this.getHivePos())); + } + +- if (this.hasSavedFlowerPos()) { +- nbt.put("flower_pos", NbtUtils.writeBlockPos(this.getSavedFlowerPos())); ++ if (includeAll && this.hasSavedFlowerPos()) { // CraftBukkit - selectively save flower ++ nbttagcompound.put("flower_pos", NbtUtils.writeBlockPos(this.getSavedFlowerPos())); + } + +- nbt.putBoolean("HasNectar", this.hasNectar()); +- nbt.putBoolean("HasStung", this.hasStung()); +- nbt.putInt("TicksSincePollination", this.ticksWithoutNectarSinceExitingHive); +- nbt.putInt("CannotEnterHiveTicks", this.stayOutOfHiveCountdown); +- nbt.putInt("CropsGrownSincePollination", this.numCropsGrownSincePollination); +- this.addPersistentAngerSaveData(nbt); ++ nbttagcompound.putBoolean("HasNectar", this.hasNectar()); ++ nbttagcompound.putBoolean("HasStung", this.hasStung()); ++ nbttagcompound.putInt("TicksSincePollination", this.ticksWithoutNectarSinceExitingHive); ++ nbttagcompound.putInt("CannotEnterHiveTicks", this.stayOutOfHiveCountdown); ++ nbttagcompound.putInt("CropsGrownSincePollination", this.numCropsGrownSincePollination); ++ this.addPersistentAngerSaveData(nbttagcompound); + } + + @Override +@@ -223,8 +250,8 @@ + this.ticksWithoutNectarSinceExitingHive = nbt.getInt("TicksSincePollination"); + this.stayOutOfHiveCountdown = nbt.getInt("CannotEnterHiveTicks"); + this.numCropsGrownSincePollination = nbt.getInt("CropsGrownSincePollination"); +- this.hivePos = (BlockPos) NbtUtils.readBlockPos(nbt, "hive_pos").orElse((Object) null); +- this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse((Object) null); ++ this.hivePos = (BlockPos) NbtUtils.readBlockPos(nbt, "hive_pos").orElse(null); // CraftBukkit - decompile error ++ this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse(null); // CraftBukkit - decompile error + this.readPersistentAngerSaveData(this.level(), nbt); + } + +@@ -248,7 +275,7 @@ + } + + if (b0 > 0) { +- entityliving.addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this); ++ entityliving.addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + +@@ -506,7 +533,12 @@ + + @Nullable + BeehiveBlockEntity getBeehiveBlockEntity() { +- return this.hivePos == null ? null : (this.isTooFarAway(this.hivePos) ? null : (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse((Object) null)); ++ // Paper start - move over logic to accommodate isTooFarAway with chunk load check ++ if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) { ++ return (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null); ++ } ++ return null; ++ // Paper end + } + + boolean isHiveValid() { +@@ -533,11 +565,13 @@ + this.setFlag(4, hasStung); + } + ++ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override + public boolean isRolling() { + return this.getFlag(2); + } + + public void setRolling(boolean nearTarget) { ++ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override + this.setFlag(2, nearTarget); + } + +@@ -602,7 +636,7 @@ + if (mobeffect != null) { + this.usePlayerItem(player, hand, itemstack); + if (!this.level().isClientSide) { +- this.addEffect(mobeffect); ++ this.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // Paper - Add missing effect cause + } + + return InteractionResult.SUCCESS; +@@ -671,8 +705,14 @@ + if (this.isInvulnerableTo(world, source)) { + return false; + } else { ++ // CraftBukkit start - Only stop pollinating if entity was damaged ++ boolean result = super.hurtServer(world, source, amount); ++ if (!result) { ++ return result; ++ } ++ // CraftBukkit end + this.beePollinateGoal.stopPollinating(); +- return super.hurtServer(world, source, amount); ++ return result; // CraftBukkit + } + } + +@@ -934,7 +974,7 @@ + Bee.this.dropFlower(); + this.pollinating = false; + Bee.this.remainingCooldownBeforeLocatingNewFlower = 200; +- } else { ++ } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this + Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D); + + if (vec3d.distanceTo(Bee.this.position()) > 1.0D) { +@@ -1082,7 +1122,7 @@ + + BeeGoToHiveGoal() { + super(); +- this.travellingTicks = Bee.this.level().random.nextInt(10); ++ this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues + this.blacklistedTargets = Lists.newArrayList(); + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } +@@ -1196,7 +1236,7 @@ + + BeeGoToKnownFlowerGoal() { + super(); +- this.travellingTicks = Bee.this.level().random.nextInt(10); ++ this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + +@@ -1301,7 +1341,7 @@ + } + } + +- if (iblockdata1 != null) { ++ if (iblockdata1 != null && CraftEventFactory.callEntityChangeBlockEvent(Bee.this, blockposition, iblockdata1)) { // CraftBukkit + Bee.this.level().levelEvent(2011, blockposition, 15); + Bee.this.level().setBlockAndUpdate(blockposition, iblockdata1); + Bee.this.incrementNumCropsGrownSincePollination(); +@@ -1378,7 +1418,7 @@ + @Override + protected void alertOther(Mob mob, LivingEntity target) { + if (mob instanceof Bee && this.mob.hasLineOfSight(target)) { +- mob.setTarget(target); ++ mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit - reason + } + + } +@@ -1387,7 +1427,7 @@ + private static class BeeBecomeAngryTargetGoal extends NearestAttackableTargetGoal { + + BeeBecomeAngryTargetGoal(Bee bee) { +- Objects.requireNonNull(bee); ++ // Objects.requireNonNull(entitybee); // CraftBukkit - decompile error + super(bee, Player.class, 10, true, false, bee::isAngryAt); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch new file mode 100644 index 0000000000..804bc3e693 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/entity/animal/Bucketable.java ++++ b/net/minecraft/world/entity/animal/Bucketable.java +@@ -16,6 +16,11 @@ + import net.minecraft.world.item.Items; + import net.minecraft.world.item.component.CustomData; + import net.minecraft.world.level.Level; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerBucketEntityEvent; ++// CraftBukkit end + + public interface Bucketable { + +@@ -93,10 +98,21 @@ + ItemStack itemstack = player.getItemInHand(hand); + + if (itemstack.getItem() == Items.WATER_BUCKET && entity.isAlive()) { +- entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F); ++ // CraftBukkit start ++ // t0.playSound(((Bucketable) t0).getPickupSound(), 1.0F, 1.0F); // CraftBukkit - moved down + ItemStack itemstack1 = ((Bucketable) entity).getBucketItemStack(); + + ((Bucketable) entity).saveToBucketTag(itemstack1); ++ ++ PlayerBucketEntityEvent playerBucketFishEvent = CraftEventFactory.callPlayerFishBucketEvent(entity, player, itemstack, itemstack1, hand); ++ itemstack1 = CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket()); ++ if (playerBucketFishEvent.isCancelled()) { ++ ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket ++ entity.resendPossiblyDesyncedEntityData((ServerPlayer) player); // Paper ++ return Optional.of(InteractionResult.FAIL); ++ } ++ entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F); ++ // CraftBukkit end + ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, itemstack1, false); + + player.setItemInHand(hand, itemstack2); +@@ -106,7 +122,7 @@ + CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer) player, itemstack1); + } + +- entity.discard(); ++ entity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + return Optional.of(InteractionResult.SUCCESS); + } else { + return Optional.empty(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch new file mode 100644 index 0000000000..d829874cb7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/world/entity/animal/Cat.java ++++ b/net/minecraft/world/entity/animal/Cat.java +@@ -174,10 +174,10 @@ + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); +- Optional optional = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("variant"))).map((minecraftkey) -> { ++ Optional> optional = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("variant"))).map((minecraftkey) -> { // CraftBukkit - decompile error + return ResourceKey.create(Registries.CAT_VARIANT, minecraftkey); + }); +- Registry iregistry = BuiltInRegistries.CAT_VARIANT; ++ Registry iregistry = BuiltInRegistries.CAT_VARIANT; // CraftBukkit - decompile error + + Objects.requireNonNull(iregistry); + optional.flatMap(iregistry::get).ifPresent(this::setVariant); +@@ -365,7 +365,7 @@ + BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagkey, world.getRandom()).ifPresent(this::setVariant); + ServerLevel worldserver = world.getLevel(); + +- if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) { ++ if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, world).isValid()) { // Paper - Fix swamp hut cat generation deadlock + this.setVariant((Holder) BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK)); + this.setPersistenceRequired(); + } +@@ -386,6 +386,13 @@ + DyeColor enumcolor = itemdye.getDyeColor(); + + if (enumcolor != this.getCollarColor()) { ++ // Paper start - Add EntityDyeEvent and CollarColorable interface ++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ enumcolor = DyeColor.byId(event.getColor().getWoolData()); ++ // Paper end - Add EntityDyeEvent and CollarColorable interface + if (!this.level().isClientSide()) { + this.setCollarColor(enumcolor); + itemstack.consume(1, player); +@@ -399,7 +406,7 @@ + this.usePlayerItem(player, hand, itemstack); + FoodProperties foodinfo = (FoodProperties) itemstack.get(DataComponents.FOOD); + +- this.heal(foodinfo != null ? (float) foodinfo.nutrition() : 1.0F); ++ this.heal(foodinfo != null ? (float) foodinfo.nutrition() : 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + this.playEatingSound(); + } + +@@ -462,7 +469,7 @@ + } + + private void tryToTame(Player player) { +- if (this.random.nextInt(3) == 0) { ++ if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit + this.tame(player); + this.setOrderedToSit(true); + this.level().broadcastEntityEvent(this, (byte) 7); +@@ -480,7 +487,7 @@ + private static class CatTemptGoal extends TemptGoal { + + @Nullable +- private Player selectedPlayer; ++ private LivingEntity selectedPlayer; // CraftBukkit + private final Cat cat; + + public CatTemptGoal(Cat cat, double speed, Predicate foodPredicate, boolean canBeScared) { +@@ -614,7 +621,15 @@ + this.cat.randomTeleport((double) (blockposition_mutableblockposition.getX() + randomsource.nextInt(11) - 5), (double) (blockposition_mutableblockposition.getY() + randomsource.nextInt(5) - 2), (double) (blockposition_mutableblockposition.getZ() + randomsource.nextInt(11) - 5), false); + blockposition_mutableblockposition.set(this.cat.blockPosition()); + this.cat.dropFromGiftLootTable(getServerLevel((Entity) this.cat), BuiltInLootTables.CAT_MORNING_GIFT, (worldserver, itemstack) -> { +- worldserver.addFreshEntity(new ItemEntity(worldserver, (double) blockposition_mutableblockposition.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack)); ++ // CraftBukkit start ++ ItemEntity entityitem = new ItemEntity(worldserver, (double) blockposition_mutableblockposition.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack); ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.cat.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ entityitem.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ worldserver.addFreshEntity(entityitem); ++ // CraftBukkit end + }); + } + +@@ -645,10 +660,10 @@ + private final Cat cat; + + public CatAvoidEntityGoal(Cat cat, Class fleeFromType, float distance, double slowSpeed, double fastSpeed) { +- Predicate predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR; ++ // Predicate predicate = IEntitySelector.NO_CREATIVE_OR_SPECTATOR; // CraftBukkit - decompile error + +- Objects.requireNonNull(predicate); +- super(cat, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test); ++ // Objects.requireNonNull(predicate); // CraftBukkit - decompile error ++ super(cat, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_CREATIVE_OR_SPECTATOR::test); // CraftBukkit - decompile error + this.cat = cat; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch new file mode 100644 index 0000000000..78c5696f2a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/animal/Chicken.java ++++ b/net/minecraft/world/entity/animal/Chicken.java +@@ -99,10 +99,12 @@ + + if (world instanceof ServerLevel worldserver) { + if (this.isAlive() && !this.isBaby() && !this.isChickenJockey() && --this.eggTime <= 0) { ++ this.forceDrops = true; // CraftBukkit + if (this.dropFromGiftLootTable(worldserver, BuiltInLootTables.CHICKEN_LAY, this::spawnAtLocation)) { + this.playSound(SoundEvents.CHICKEN_EGG, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); + this.gameEvent(GameEvent.ENTITY_PLACE); + } ++ this.forceDrops = false; // CraftBukkit + + this.eggTime = this.random.nextInt(6000) + 6000; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch new file mode 100644 index 0000000000..4e23f7179b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/entity/animal/Cow.java ++++ b/net/minecraft/world/entity/animal/Cow.java +@@ -30,6 +30,11 @@ + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.player.PlayerBucketFillEvent; ++// CraftBukkit end + + public class Cow extends Animal { + +@@ -92,8 +97,17 @@ + ItemStack itemstack = player.getItemInHand(hand); + + if (itemstack.is(Items.BUCKET) && !this.isBaby()) { ++ // CraftBukkit start - Got milk? ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); ++ ++ if (event.isCancelled()) { ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end ++ + player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F); +- ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance()); ++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit + + player.setItemInHand(hand, itemstack1); + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch new file mode 100644 index 0000000000..c16913343e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch @@ -0,0 +1,87 @@ +--- a/net/minecraft/world/entity/animal/Dolphin.java ++++ b/net/minecraft/world/entity/animal/Dolphin.java +@@ -61,9 +61,20 @@ + import net.minecraft.world.level.ServerLevelAccessor; + import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Dolphin extends AgeableWaterCreature { + ++ // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ @Override ++ public int getDefaultMaxAirSupply() { ++ return Dolphin.TOTAL_AIR_SUPPLY; ++ } ++ // CraftBukkit end + private static final EntityDataAccessor TREASURE_POS = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BLOCK_POS); + private static final EntityDataAccessor GOT_FISH = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor MOISTNESS_LEVEL = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.INT); +@@ -200,7 +211,7 @@ + + @Override + public int getMaxAirSupply() { +- return 4800; ++ return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + } + + @Override +@@ -234,11 +245,17 @@ + ItemStack itemstack = itemEntity.getItem(); + + if (this.canHoldItem(itemstack)) { ++ // CraftBukkit start - call EntityPickupItemEvent ++ if (CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) { ++ return; ++ } ++ itemstack = itemEntity.getItem(); // CraftBukkit- update ItemStack from event ++ // CraftBukkit start + this.onItemPickup(itemEntity); + this.setItemSlot(EquipmentSlot.MAINHAND, itemstack); + this.setGuaranteedDrop(EquipmentSlot.MAINHAND); + this.take(itemEntity, itemstack.getCount()); +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } + } + +@@ -332,7 +349,7 @@ + + @Nullable + @Override +- protected SoundEvent getDeathSound() { ++ public SoundEvent getDeathSound() { // Paper - decompile error + return SoundEvents.DOLPHIN_DEATH; + } + +@@ -495,7 +512,7 @@ + + @Override + public void start() { +- this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin); ++ this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit + } + + @Override +@@ -514,7 +531,7 @@ + } + + if (this.player.isSwimming() && this.player.level().random.nextInt(6) == 0) { +- this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin); ++ this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit + } + + } +@@ -587,7 +604,7 @@ + float f2 = 0.02F * Dolphin.this.random.nextFloat(); + + entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); +- Dolphin.this.level().addFreshEntity(entityitem); ++ Dolphin.this.spawnAtLocation(getServerLevel(Dolphin.this), entityitem); // Paper - Call EntityDropItemEvent + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch new file mode 100644 index 0000000000..7052ecb52a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch @@ -0,0 +1,168 @@ +--- a/net/minecraft/world/entity/animal/Fox.java ++++ b/net/minecraft/world/entity/animal/Fox.java +@@ -90,6 +90,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Fox extends Animal implements VariantHolder { + +@@ -416,7 +419,7 @@ + + this.setSleeping(nbt.getBoolean("Sleeping")); + this.setVariant(Fox.Variant.byName(nbt.getString("Type"))); +- this.setSitting(nbt.getBoolean("Sitting")); ++ this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent + this.setIsCrouching(nbt.getBoolean("Crouching")); + if (this.level() instanceof ServerLevel) { + this.setTargetGoals(); +@@ -429,6 +432,12 @@ + } + + public void setSitting(boolean sitting) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setSitting(sitting, true); ++ } ++ public void setSitting(boolean sitting, boolean fireEvent) { ++ if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + this.setFlag(1, sitting); + } + +@@ -489,21 +498,22 @@ + entityitem.setPickUpDelay(40); + entityitem.setThrower(this); + this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent + } + } + + private void dropItemStack(ItemStack stack) { + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack); + +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent + } + + @Override + protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) { + ItemStack itemstack = itemEntity.getItem(); + +- if (this.canHoldItem(itemstack)) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, itemstack.getCount() - 1, !this.canHoldItem(itemstack)).isCancelled()) { // CraftBukkit - call EntityPickupItemEvent ++ itemstack = itemEntity.getItem(); // CraftBukkit - update ItemStack from event + int i = itemstack.getCount(); + + if (i > 1) { +@@ -515,7 +525,7 @@ + this.setItemSlot(EquipmentSlot.MAINHAND, itemstack.split(1)); + this.setGuaranteedDrop(EquipmentSlot.MAINHAND); + this.take(itemEntity, itemstack.getCount()); +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + this.ticksSinceEaten = 0; + } + +@@ -685,16 +695,38 @@ + return this.getTrustedUUIDs().contains(uuid); + } + ++ // Paper start - handle the bitten item separately like vanilla + @Override +- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { ++ return slot == EquipmentSlot.MAINHAND; ++ } ++ // Paper end ++ ++ @Override ++ // Paper start - Cancellable death event ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); + +- if (!itemstack.isEmpty()) { ++ boolean releaseMouth = false; ++ if (!itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010 + this.spawnAtLocation(world, itemstack); ++ releaseMouth = true; ++ } ++ ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(world, damageSource); ++ ++ // Below is code to drop ++ ++ if (deathEvent == null || deathEvent.isCancelled()) { ++ return deathEvent; ++ } ++ ++ if (releaseMouth) { ++ // Paper end - Cancellable death event + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + +- super.dropAllDeathLoot(world, damageSource); ++ return deathEvent; // Paper - Cancellable death event + } + + public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) { +@@ -853,6 +885,16 @@ + if (entityplayer1 != null && entityplayer != entityplayer1) { + entityfox.addTrustedUUID(entityplayer1.getUUID()); + } ++ // CraftBukkit start - call EntityBreedEvent ++ entityfox.setAge(-24000); ++ entityfox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F); ++ int experience = this.animal.getRandom().nextInt(7) + 1; ++ org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityfox, this.animal, this.partner, entityplayer, this.animal.breedItem, experience); ++ if (entityBreedEvent.isCancelled()) { ++ return; ++ } ++ experience = entityBreedEvent.getExperience(); ++ // CraftBukkit end + + if (entityplayer2 != null) { + entityplayer2.awardStat(Stats.ANIMALS_BRED); +@@ -863,12 +905,14 @@ + this.partner.setAge(6000); + this.animal.resetLove(); + this.partner.resetLove(); +- entityfox.setAge(-24000); +- entityfox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F); +- worldserver.addFreshEntityWithPassengers(entityfox); ++ worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason + this.level.broadcastEntityEvent(this.animal, (byte) 18); + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), this.animal.getRandom().nextInt(7) + 1)); ++ // CraftBukkit start - use event experience ++ if (experience > 0) { ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper ++ } ++ // CraftBukkit end + } + + } +@@ -1264,6 +1308,11 @@ + int i = (Integer) state.getValue(SweetBerryBushBlock.AGE); + + state.setValue(SweetBerryBushBlock.AGE, 1); ++ // CraftBukkit start - call EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(Fox.this, this.blockPos, state.setValue(SweetBerryBushBlock.AGE, 1))) { ++ return; ++ } ++ // CraftBukkit end + int j = 1 + Fox.this.level().random.nextInt(2) + (i == 3 ? 1 : 0); + ItemStack itemstack = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND); + +@@ -1494,7 +1543,7 @@ + } + + public static Fox.Variant byName(String name) { +- return (Fox.Variant) Fox.Variant.CODEC.byName(name, (Enum) Fox.Variant.RED); ++ return (Fox.Variant) Fox.Variant.CODEC.byName(name, Fox.Variant.RED); // CraftBukkit - decompile error + } + + public static Fox.Variant byId(int id) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch new file mode 100644 index 0000000000..4562593cc0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/entity/animal/IronGolem.java ++++ b/net/minecraft/world/entity/animal/IronGolem.java +@@ -98,7 +98,7 @@ + @Override + protected void doPush(Entity entity) { + if (entity instanceof Enemy && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) { +- this.setTarget((LivingEntity) entity); ++ this.setTarget((LivingEntity) entity, org.bukkit.event.entity.EntityTargetLivingEntityEvent.TargetReason.COLLISION, true); // CraftBukkit - set reason + } + + super.doPush(entity); +@@ -319,7 +319,7 @@ + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata = world.getBlockState(blockposition1); + +- if (!iblockdata.entityCanStandOn(world, blockposition1, this)) { ++ if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !this.level().paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - Add option to allow iron golems to spawn in air + return false; + } else { + for (int i = 1; i < 3; ++i) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch new file mode 100644 index 0000000000..a7cff6ad18 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch @@ -0,0 +1,85 @@ +--- a/net/minecraft/world/entity/animal/MushroomCow.java ++++ b/net/minecraft/world/entity/animal/MushroomCow.java +@@ -42,6 +42,13 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.entity.EntityDropItemEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++// CraftBukkit end + + public class MushroomCow extends Cow implements Shearable, VariantHolder { + +@@ -120,7 +127,19 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ // CraftBukkit start ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops ++ } ++ // CraftBukkit end ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -158,16 +177,32 @@ + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (ignored, stack) -> { ++ for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1)); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), (entitycow) -> { + world.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (worldserver1, itemstack1) -> { +- for (int i = 0; i < itemstack1.getCount(); ++i) { +- worldserver1.addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), itemstack1.copyWithCount(1))); +- } +- ++ // Paper start - custom shear drops; moved drop generation to separate method ++ drops.forEach(drop -> { ++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop); ++ this.spawnAtLocation(world, entityitem); ++ // Paper end - custom shear drops; moved drop generation to separate method + }); +- }); ++ }, EntityTransformEvent.TransformReason.SHEARED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHEARED); // CraftBukkit + } + + @Override +@@ -263,7 +298,7 @@ + } + + static MushroomCow.Variant byName(String name) { +- return (MushroomCow.Variant) MushroomCow.Variant.CODEC.byName(name, (Enum) MushroomCow.Variant.RED); ++ return (MushroomCow.Variant) MushroomCow.Variant.CODEC.byName(name, MushroomCow.Variant.RED); // CraftBukkit - decompile error + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch new file mode 100644 index 0000000000..e217641cf1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/entity/animal/Ocelot.java ++++ b/net/minecraft/world/entity/animal/Ocelot.java +@@ -132,7 +132,7 @@ + + @Override + public boolean removeWhenFarAway(double distanceSquared) { +- return !this.isTrusting() && this.tickCount > 2400; ++ return !this.isTrusting() && this.tickCount > 2400 && !this.hasCustomName() && !this.isLeashed(); // Paper - honor name and leash + } + + public static AttributeSupplier.Builder createAttributes() { +@@ -167,7 +167,7 @@ + if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemstack) && player.distanceToSqr((Entity) this) < 9.0D) { + this.usePlayerItem(player, hand, itemstack); + if (!this.level().isClientSide) { +- if (this.random.nextInt(3) == 0) { ++ if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check + this.setTrusting(true); + this.spawnTrustingParticles(true); + this.level().broadcastEntityEvent(this, (byte) 41); +@@ -298,10 +298,10 @@ + private final Ocelot ocelot; + + public OcelotAvoidEntityGoal(Ocelot ocelot, Class fleeFromType, float distance, double slowSpeed, double fastSpeed) { +- Predicate predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR; ++ // Predicate predicate = IEntitySelector.NO_CREATIVE_OR_SPECTATOR; // CraftBukkit - decompile error + +- Objects.requireNonNull(predicate); +- super(ocelot, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test); ++ // Objects.requireNonNull(predicate); // CraftBukkit - decompile error ++ super(ocelot, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_CREATIVE_OR_SPECTATOR::test); // CraftBukkit - decompile error + this.ocelot = ocelot; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch new file mode 100644 index 0000000000..911c02e2c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch @@ -0,0 +1,122 @@ +--- a/net/minecraft/world/entity/animal/Panda.java ++++ b/net/minecraft/world/entity/animal/Panda.java +@@ -68,6 +68,11 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public class Panda extends Animal { + +@@ -129,6 +134,7 @@ + } + + public void sit(boolean sitting) { ++ if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setFlag(8, sitting); + } + +@@ -525,7 +531,9 @@ + Panda entitypanda = (Panda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + entitypanda.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + +@@ -533,7 +541,9 @@ + + if (world1 instanceof ServerLevel worldserver) { + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.dropFromGiftLootTable(worldserver, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + } + +@@ -541,14 +551,14 @@ + + @Override + protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) { +- if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(itemEntity)) { ++ if (!CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, !(this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(itemEntity))).isCancelled()) { // CraftBukkit + this.onItemPickup(itemEntity); + ItemStack itemstack = itemEntity.getItem(); + + this.setItemSlot(EquipmentSlot.MAINHAND, itemstack); + this.setGuaranteedDrop(EquipmentSlot.MAINHAND); + this.take(itemEntity, itemstack.getCount()); +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -643,8 +653,9 @@ + this.usePlayerItem(player, hand, itemstack); + this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true); + } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + } else { + Level world = this.level(); + +@@ -657,7 +668,9 @@ + ItemStack itemstack1 = this.getItemBySlot(EquipmentSlot.MAINHAND); + + if (!itemstack1.isEmpty() && !player.hasInfiniteMaterials()) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.spawnAtLocation(worldserver, itemstack1); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemstack.getItem(), 1)); +@@ -772,7 +785,7 @@ + } + + public static Panda.Gene byName(String name) { +- return (Panda.Gene) Panda.Gene.CODEC.byName(name, (Enum) Panda.Gene.NORMAL); ++ return (Panda.Gene) Panda.Gene.CODEC.byName(name, Panda.Gene.NORMAL); // CraftBukkit - decompile error + } + + public static Panda.Gene getRandom(RandomSource random) { +@@ -876,10 +889,10 @@ + private final Panda panda; + + public PandaAvoidGoal(Panda panda, Class fleeFromType, float distance, double slowSpeed, double fastSpeed) { +- Predicate predicate = EntitySelector.NO_SPECTATORS; ++ // Predicate predicate = IEntitySelector.NO_SPECTATORS; + +- Objects.requireNonNull(predicate); +- super(panda, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test); ++ // Objects.requireNonNull(predicate); ++ super(panda, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_SPECTATORS::test); + this.panda = panda; + } + +@@ -935,7 +948,9 @@ + ItemStack itemstack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND); + + if (!itemstack.isEmpty()) { ++ Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles + Panda.this.spawnAtLocation(getServerLevel(Panda.this.level()), itemstack); ++ Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles + Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10; + +@@ -1116,7 +1131,7 @@ + @Override + protected void alertOther(Mob mob, LivingEntity target) { + if (mob instanceof Panda && mob.isAggressive()) { +- mob.setTarget(target); ++ mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch new file mode 100644 index 0000000000..e42863c2b1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch @@ -0,0 +1,47 @@ +--- a/net/minecraft/world/entity/animal/Parrot.java ++++ b/net/minecraft/world/entity/animal/Parrot.java +@@ -248,7 +248,7 @@ + } + + if (!this.level().isClientSide) { +- if (this.random.nextInt(10) == 0) { ++ if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit + this.tame(player); + this.level().broadcastEntityEvent(this, (byte) 7); + } else { +@@ -269,7 +269,7 @@ + } + } else { + this.usePlayerItem(player, hand, itemstack); +- this.addEffect(new MobEffectInstance(MobEffects.POISON, 900)); ++ this.addEffect(new MobEffectInstance(MobEffects.POISON, 900), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit + if (player.isCreative() || !this.isInvulnerable()) { + this.hurt(this.damageSources().playerAttack(player), Float.MAX_VALUE); + } +@@ -362,8 +362,8 @@ + } + + @Override +- public boolean isPushable() { +- return true; ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule ++ return super.isCollidable(ignoreClimbing); // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule + } + + @Override +@@ -378,8 +378,14 @@ + if (this.isInvulnerableTo(world, source)) { + return false; + } else { ++ // CraftBukkit start ++ boolean result = super.hurtServer(world, source, amount); ++ if (!result) { ++ return result; ++ } ++ // CraftBukkit end + this.setOrderedToSit(false); +- return super.hurtServer(world, source, amount); ++ return result; // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch new file mode 100644 index 0000000000..a5cdaa0866 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/entity/animal/Pig.java ++++ b/net/minecraft/world/entity/animal/Pig.java +@@ -49,6 +49,10 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Pig extends Animal implements ItemSteerable, Saddleable { + +@@ -247,7 +251,14 @@ + } + + entitypigzombie1.setPersistenceRequired(); +- }); ++ // CraftBukkit start ++ }, null, null); ++ if (CraftEventFactory.callPigZapEvent(this, lightning, entitypigzombie).isCancelled()) { ++ return; ++ } ++ world.addFreshEntity(entitypigzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); ++ this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause ++ // CraftBukkit end + + if (entitypigzombie == null) { + super.thunderHit(world, lightning); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch new file mode 100644 index 0000000000..32042a8ba6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/world/entity/animal/Pufferfish.java ++++ b/net/minecraft/world/entity/animal/Pufferfish.java +@@ -102,25 +102,39 @@ + public void tick() { + if (!this.level().isClientSide && this.isAlive() && this.isEffectiveAi()) { + if (this.inflateCounter > 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.getPuffState() == 0) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); + this.setPuffState(2); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.inflateCounter; ++ } // Paper - Add PufferFishStateChangeEvent + } else if (this.getPuffState() != 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.deflateTimer > 60 && this.getPuffState() == 2) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); + this.setPuffState(0); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.deflateTimer; ++ } // Paper - Add PufferFishStateChangeEvent + } + } + +@@ -155,7 +169,7 @@ + int i = this.getPuffState(); + + if (target.hurtServer(world, this.damageSources().mobAttack(this), (float) (1 + i))) { +- target.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this); ++ target.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + this.playSound(SoundEvents.PUFFER_FISH_STING, 1.0F, 1.0F); + } + +@@ -171,7 +185,7 @@ + entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PUFFER_FISH_STING, 0.0F)); + } + +- player.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this); ++ player.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch new file mode 100644 index 0000000000..1f8f84be30 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/world/entity/animal/Rabbit.java ++++ b/net/minecraft/world/entity/animal/Rabbit.java +@@ -65,6 +65,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class Rabbit extends Animal implements VariantHolder { + +@@ -90,7 +93,6 @@ + super(type, world); + this.jumpControl = new Rabbit.RabbitJumpControl(this); + this.moveControl = new Rabbit.RabbitMoveControl(this); +- this.setSpeedModifier(0.0D); + } + + @Override +@@ -577,9 +579,19 @@ + int i = (Integer) iblockdata.getValue(CarrotBlock.AGE); + + if (i == 0) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ return; ++ } ++ // CraftBukkit end + world.setBlock(blockposition, Blocks.AIR.defaultBlockState(), 2); + world.destroyBlock(blockposition, true, this.rabbit); + } else { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.setValue(CarrotBlock.AGE, i - 1))) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(blockposition, (BlockState) iblockdata.setValue(CarrotBlock.AGE, i - 1), 2); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of((Entity) this.rabbit)); + world.levelEvent(2001, blockposition, Block.getId(iblockdata)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch new file mode 100644 index 0000000000..ce52e0ef57 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch @@ -0,0 +1,90 @@ +--- a/net/minecraft/world/entity/animal/Sheep.java ++++ b/net/minecraft/world/entity/animal/Sheep.java +@@ -41,7 +41,6 @@ + import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; +-import net.minecraft.world.item.DyeColor; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; +@@ -49,6 +48,12 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; ++import net.minecraft.world.item.DyeColor; ++// CraftBukkit start ++import net.minecraft.world.item.Item; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.SheepRegrowWoolEvent; ++// CraftBukkit end + + public class Sheep extends Animal implements Shearable { + +@@ -160,7 +165,19 @@ + ServerLevel worldserver = (ServerLevel) world; + + if (this.readyForShearing()) { +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ // CraftBukkit start ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops ++ } ++ // CraftBukkit end ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + return InteractionResult.SUCCESS_SERVER; +@@ -175,10 +192,29 @@ + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SHEEP, shears, (ignored, stack) -> { ++ for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1)); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SHEEP, shears, (worldserver1, itemstack1) -> { +- for (int i = 0; i < itemstack1.getCount(); ++i) { +- ItemEntity entityitem = this.spawnAtLocation(worldserver1, itemstack1.copyWithCount(1), 1.0F); ++ drops.forEach(itemstack1 -> { // Paper - custom drops - loop in generated default drops ++ if (true) { // Paper - custom drops - loop in generated default drops ++ this.forceDrops = true; // CraftBukkit ++ ItemEntity entityitem = this.spawnAtLocation(worldserver1, itemstack1, 1.0F); // Paper - custom drops - copy already done above ++ this.forceDrops = false; // CraftBukkit + + if (entityitem != null) { + entityitem.setDeltaMovement(entityitem.getDeltaMovement().add((double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F), (double) (this.random.nextFloat() * 0.05F), (double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F))); +@@ -276,6 +312,12 @@ + + @Override + public void ate() { ++ // CraftBukkit start ++ SheepRegrowWoolEvent event = new SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end + super.ate(); + this.setSheared(false); + if (this.isBaby()) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch new file mode 100644 index 0000000000..40307984a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/animal/ShoulderRidingEntity.java ++++ b/net/minecraft/world/entity/animal/ShoulderRidingEntity.java +@@ -5,6 +5,9 @@ + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.TamableAnimal; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public abstract class ShoulderRidingEntity extends TamableAnimal { + +@@ -21,7 +24,7 @@ + nbttagcompound.putString("id", this.getEncodeId()); + this.saveWithoutId(nbttagcompound); + if (player.setEntityOnShoulder(nbttagcompound)) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + return true; + } else { + return false; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch new file mode 100644 index 0000000000..c07f86335e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch @@ -0,0 +1,86 @@ +--- a/net/minecraft/world/entity/animal/SnowGolem.java ++++ b/net/minecraft/world/entity/animal/SnowGolem.java +@@ -42,6 +42,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob { + +@@ -100,7 +103,7 @@ + + if (world instanceof ServerLevel worldserver) { + if (this.level().getBiome(this.blockPosition()).is(BiomeTags.SNOW_GOLEM_MELTS)) { +- this.hurtServer(worldserver, this.damageSources().onFire(), 1.0F); ++ this.hurtServer(worldserver, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING + } + + if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { +@@ -116,7 +119,11 @@ + BlockPos blockposition = new BlockPos(j, k, l); + + if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) { +- this.level().setBlockAndUpdate(blockposition, iblockdata); ++ // CraftBukkit start ++ if (!CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, this)) { ++ continue; ++ } ++ // CraftBukkit end + this.level().gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this, iblockdata)); + } + } +@@ -153,7 +160,19 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ // CraftBukkit start ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops ++ } ++ // CraftBukkit end ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -166,10 +185,29 @@ + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (ignored, stack) -> { ++ drops.add(stack); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.setPumpkin(false); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (worldserver1, itemstack1) -> { ++ drops.forEach(itemstack1 -> { // Paper - custom shear drops ++ this.forceDrops = true; // CraftBukkit + this.spawnAtLocation(worldserver1, itemstack1, this.getEyeHeight()); ++ this.forceDrops = false; // CraftBukkit + }); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch new file mode 100644 index 0000000000..d67bfc9654 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/animal/Squid.java ++++ b/net/minecraft/world/entity/animal/Squid.java +@@ -46,7 +46,7 @@ + + public Squid(EntityType type, Level world) { + super(type, world); +- this.random.setSeed((long)this.getId()); ++ //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random + this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch new file mode 100644 index 0000000000..49bba2e107 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/entity/animal/Turtle.java ++++ b/net/minecraft/world/entity/animal/Turtle.java +@@ -310,7 +310,9 @@ + ServerLevel worldserver = (ServerLevel) world; + + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ this.forceDrops = true; // CraftBukkit + this.spawnAtLocation(worldserver, Items.TURTLE_SCUTE, 1); ++ this.forceDrops = false; // CraftBukkit + } + } + } +@@ -339,7 +341,7 @@ + + @Override + public void thunderHit(ServerLevel world, LightningBolt lightning) { +- this.hurtServer(world, this.damageSources().lightningBolt(), Float.MAX_VALUE); ++ this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), Float.MAX_VALUE); // CraftBukkit // Paper - fix DamageSource API + } + + @Override +@@ -446,6 +448,10 @@ + if (entityplayer == null && this.partner.getLoveCause() != null) { + entityplayer = this.partner.getLoveCause(); + } ++ // Paper start - Add EntityFertilizeEggEvent event ++ io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner); ++ if (event.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event + + if (entityplayer != null) { + entityplayer.awardStat(Stats.ANIMALS_BRED); +@@ -460,7 +466,7 @@ + RandomSource randomsource = this.animal.getRandom(); + + if (getServerLevel((Level) this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1)); ++ if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event + } + + } +@@ -492,16 +498,21 @@ + + if (!this.turtle.isInWater() && this.isReachedTarget()) { + if (this.turtle.layEggCounter < 1) { +- this.turtle.setLayingEgg(true); ++ this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API + } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { + Level world = this.turtle.level(); + ++ // Paper start - Turtle API ++ int eggCount = this.turtle.random.nextInt(4) + 1; ++ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount); ++ if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) { + world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); + BlockPos blockposition1 = this.blockPos.above(); +- BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); ++ BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper + + world.setBlock(blockposition1, iblockdata, 3); + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata)); ++ } // CraftBukkit + this.turtle.setHasEgg(false); + this.turtle.setLayingEgg(false); + this.turtle.setInLoveTime(600); +@@ -567,7 +578,7 @@ + + @Override + public boolean canUse() { +- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))); ++ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch new file mode 100644 index 0000000000..5089fd98c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/entity/animal/WaterAnimal.java ++++ b/net/minecraft/world/entity/animal/WaterAnimal.java +@@ -70,6 +70,10 @@ + ) { + int i = world.getSeaLevel(); + int j = i - 13; ++ // Paper start - Make water animal spawn height configurable ++ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); ++ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); ++ // Paper end - Make water animal spawn height configurable + return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch new file mode 100644 index 0000000000..50375e02de --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch @@ -0,0 +1,126 @@ +--- a/net/minecraft/world/entity/animal/Wolf.java ++++ b/net/minecraft/world/entity/animal/Wolf.java +@@ -85,6 +85,12 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityDamageEvent; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder> { + +@@ -345,8 +351,14 @@ + if (this.isInvulnerableTo(world, source)) { + return false; + } else { ++ // CraftBukkit start ++ boolean result = super.hurtServer(world, source, amount); ++ if (!result) { ++ return result; ++ } ++ // CraftBukkit end + this.setOrderedToSit(false); +- return super.hurtServer(world, source, amount); ++ return result; // CraftBukkit + } + } + +@@ -356,21 +368,27 @@ + } + + @Override +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { +- if (!this.canArmorAbsorb(source)) { +- super.actuallyHurt(world, source, amount); ++ public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { // CraftBukkit - void -> boolean ++ if (!this.canArmorAbsorb(damagesource)) { ++ return super.actuallyHurt(worldserver, damagesource, f, event); // CraftBukkit + } else { ++ // CraftBukkit start - SPIGOT-7815: if the damage was cancelled, no need to run the wolf armor behaviour ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + ItemStack itemstack = this.getBodyArmorItem(); + int i = itemstack.getDamageValue(); + int j = itemstack.getMaxDamage(); + +- itemstack.hurtAndBreak(Mth.ceil(amount), this, EquipmentSlot.BODY); ++ itemstack.hurtAndBreak(Mth.ceil(f), this, EquipmentSlot.BODY); + if (Crackiness.WOLF_ARMOR.byDamage(i, j) != Crackiness.WOLF_ARMOR.byDamage(this.getBodyArmorItem())) { + this.playSound(SoundEvents.WOLF_ARMOR_CRACK); +- world.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()), this.getX(), this.getY() + 1.0D, this.getZ(), 20, 0.2D, 0.1D, 0.2D, 0.1D); ++ worldserver.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()), this.getX(), this.getY() + 1.0D, this.getZ(), 20, 0.2D, 0.1D, 0.2D, 0.1D); + } + + } ++ return true; // CraftBukkit // Paper - return false ONLY if event was cancelled + } + + private boolean canArmorAbsorb(DamageSource source) { +@@ -381,7 +399,7 @@ + protected void applyTamingSideEffects() { + if (this.isTame()) { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(40.0D); +- this.setHealth(40.0F); ++ this.setHealth(this.getMaxHealth()); // CraftBukkit - 40.0 -> getMaxHealth() + } else { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0D); + } +@@ -404,7 +422,7 @@ + FoodProperties foodinfo = (FoodProperties) itemstack.get(DataComponents.FOOD); + float f = foodinfo != null ? (float) foodinfo.nutrition() : 1.0F; + +- this.heal(2.0F * f); ++ this.heal(2.0F * f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit + return InteractionResult.SUCCESS; + } else { + if (item instanceof DyeItem) { +@@ -414,6 +432,14 @@ + DyeColor enumcolor = itemdye.getDyeColor(); + + if (enumcolor != this.getCollarColor()) { ++ // Paper start - Add EntityDyeEvent and CollarColorable interface ++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ enumcolor = DyeColor.byId(event.getColor().getWoolData()); ++ // Paper end - Add EntityDyeEvent and CollarColorable interface ++ + this.setCollarColor(enumcolor); + itemstack.consume(1, player); + return InteractionResult.SUCCESS; +@@ -440,7 +466,9 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + ++ this.forceDrops = true; // CraftBukkit + this.spawnAtLocation(worldserver, itemstack1); ++ this.forceDrops = false; // CraftBukkit + } + + return InteractionResult.SUCCESS; +@@ -459,7 +487,7 @@ + this.setOrderedToSit(!this.isOrderedToSit()); + this.jumping = false; + this.navigation.stop(); +- this.setTarget((LivingEntity) null); ++ this.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason + return InteractionResult.SUCCESS.withoutItem(); + } else { + return enuminteractionresult; +@@ -477,7 +505,8 @@ + } + + private void tryToTame(Player player) { +- if (this.random.nextInt(3) == 0) { ++ // CraftBukkit - added event call and isCancelled check. ++ if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { + this.tame(player); + this.navigation.stop(); + this.setTarget((LivingEntity) null); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch new file mode 100644 index 0000000000..8c853dc764 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch @@ -0,0 +1,102 @@ +--- a/net/minecraft/world/entity/animal/allay/Allay.java ++++ b/net/minecraft/world/entity/animal/allay/Allay.java +@@ -103,6 +103,7 @@ + private float dancingAnimationTicks; + private float spinningAnimationTicks; + private float spinningAnimationTicks0; ++ public boolean forceDancing = false; // CraftBukkit + + public Allay(EntityType type, Level world) { + super(type, world); +@@ -114,6 +115,12 @@ + this.dynamicJukeboxListener = new DynamicGameEventListener<>(new Allay.JukeboxListener(this.vibrationUser.getPositionSource(), ((GameEvent) GameEvent.JUKEBOX_PLAY.value()).notificationRadius())); + } + ++ // CraftBukkit start ++ public void setCanDuplicate(boolean canDuplicate) { ++ this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate); ++ } ++ // CraftBukkit end ++ + @Override + protected Brain.Provider brainProvider() { + return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); +@@ -126,7 +133,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + public static AttributeSupplier.Builder createAttributes() { +@@ -233,7 +240,7 @@ + public void aiStep() { + super.aiStep(); + if (!this.level().isClientSide && this.isAlive() && this.tickCount % 10 == 0) { +- this.heal(1.0F); ++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + } + + if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) { +@@ -303,7 +310,12 @@ + ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND); + + if (this.isDancing() && itemstack.is(ItemTags.DUPLICATES_ALLAYS) && this.canDuplicate()) { +- this.duplicateAllay(); ++ // CraftBukkit start - handle cancel duplication ++ Allay allay = this.duplicateAllay(); ++ if (allay == null) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + this.level().broadcastEntityEvent(this, (byte) 18); + this.level().playSound(player, (Entity) this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F); + this.removeInteractionItem(player, itemstack); +@@ -314,7 +326,7 @@ + this.setItemInHand(InteractionHand.MAIN_HAND, itemstack2); + this.removeInteractionItem(player, itemstack); + this.level().playSound(player, (Entity) this, SoundEvents.ALLAY_ITEM_GIVEN, SoundSource.NEUTRAL, 2.0F, 1.0F); +- this.getBrain().setMemory(MemoryModuleType.LIKED_PLAYER, (Object) player.getUUID()); ++ this.getBrain().setMemory(MemoryModuleType.LIKED_PLAYER, player.getUUID()); // CraftBukkit - decompile error + return InteractionResult.SUCCESS; + } else if (!itemstack1.isEmpty() && hand == InteractionHand.MAIN_HAND && itemstack.isEmpty()) { + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); +@@ -415,6 +427,7 @@ + } + + private boolean shouldStopDancing() { ++ if (this.forceDancing) {return false;} // CraftBukkit + return this.jukeboxPos == null || !this.jukeboxPos.closerToCenterThan(this.position(), (double) ((GameEvent) GameEvent.JUKEBOX_PLAY.value()).notificationRadius()) || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX); + } + +@@ -486,7 +499,7 @@ + }); + } + +- this.duplicationCooldown = (long) nbt.getInt("DuplicationCooldown"); ++ this.duplicationCooldown = nbt.getLong("DuplicationCooldown"); // Paper - Load as long + this.entityData.set(Allay.DATA_CAN_DUPLICATE, nbt.getBoolean("CanDuplicate")); + } + +@@ -506,7 +519,7 @@ + + } + +- public void duplicateAllay() { ++ public Allay duplicateAllay() { // CraftBukkit - return allay + Allay allay = (Allay) EntityType.ALLAY.create(this.level(), EntitySpawnReason.BREEDING); + + if (allay != null) { +@@ -514,9 +527,9 @@ + allay.setPersistenceRequired(); + allay.resetDuplicationCooldown(); + this.resetDuplicationCooldown(); +- this.level().addFreshEntity(allay); ++ this.level().addFreshEntity(allay, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DUPLICATION); // CraftBukkit - reason for duplicated allay + } +- ++ return allay; // CraftBukkit + } + + public void resetDuplicationCooldown() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch new file mode 100644 index 0000000000..2e72adb5ea --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java ++++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +@@ -47,6 +47,9 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityDamageEvent; ++// CraftBukkit end + + public class Armadillo extends Animal { + +@@ -135,16 +138,18 @@ + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("armadilloBrain"); +- this.brain.tick(world, this); ++ ((Brain) this.brain).tick(world, this); // CraftBukkit - decompile error + gameprofilerfiller.pop(); + gameprofilerfiller.push("armadilloActivityUpdate"); + ArmadilloAi.updateActivity(this); + gameprofilerfiller.pop(); + if (this.isAlive() && !this.isBaby() && --this.scuteTime <= 0) { ++ this.forceDrops = true; // CraftBukkit + if (this.dropFromGiftLootTable(world, BuiltInLootTables.ARMADILLO_SHED, this::spawnAtLocation)) { + this.playSound(SoundEvents.ARMADILLO_SCUTE_DROP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); + this.gameEvent(GameEvent.ENTITY_PLACE); + } ++ this.forceDrops = false; // CraftBukkit + + this.scuteTime = this.pickNextScuteDropTime(); + } +@@ -291,19 +296,25 @@ + } + + @Override +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { +- super.actuallyHurt(world, source, amount); ++ // CraftBukkit start - void -> boolean ++ public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { ++ boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event); ++ if (!damageResult) { ++ return false; ++ } ++ // CraftBukkit end + if (!this.isNoAi() && !this.isDeadOrDying()) { +- if (source.getEntity() instanceof LivingEntity) { ++ if (damagesource.getEntity() instanceof LivingEntity) { + this.getBrain().setMemoryWithExpiry(MemoryModuleType.DANGER_DETECTED_RECENTLY, true, 80L); + if (this.canStayRolledUp()) { + this.rollUp(); + } +- } else if (source.is(DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)) { ++ } else if (damagesource.is(DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)) { + this.rollOut(); + } + + } ++ return true; // CraftBukkit + } + + @Override +@@ -327,7 +338,9 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + ++ this.forceDrops = true; // CraftBukkit + this.spawnAtLocation(worldserver, new ItemStack(Items.ARMADILLO_SCUTE)); ++ this.forceDrops = false; // CraftBukkit + this.gameEvent(GameEvent.ENTITY_INTERACT); + this.playSound(SoundEvents.ARMADILLO_BRUSH); + } +@@ -431,7 +444,7 @@ + } + + public static Armadillo.ArmadilloState fromName(String name) { +- return (Armadillo.ArmadilloState) Armadillo.ArmadilloState.CODEC.byName(name, (Enum) Armadillo.ArmadilloState.IDLE); ++ return (Armadillo.ArmadilloState) Armadillo.ArmadilloState.CODEC.byName(name, Armadillo.ArmadilloState.IDLE); // CraftBukkit - decompile error + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch new file mode 100644 index 0000000000..3923bd7b5c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java ++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java +@@ -67,10 +67,17 @@ + + public class Axolotl extends Animal implements VariantHolder, Bucketable { + ++ // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ @Override ++ public int getDefaultMaxAirSupply() { ++ return Axolotl.AXOLOTL_TOTAL_AIR_SUPPLY; ++ } ++ // CraftBukkit end + public static final int TOTAL_PLAYDEAD_TIME = 200; + private static final int POSE_ANIMATION_TICKS = 10; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_ADULT, SensorType.HURT_BY, SensorType.AXOLOTL_ATTACKABLES, SensorType.AXOLOTL_TEMPTATIONS); +- protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT, new MemoryModuleType[]{MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.PLAY_DEAD_TICKS, MemoryModuleType.NEAREST_ATTACKABLE, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HAS_HUNTING_COOLDOWN, MemoryModuleType.IS_PANICKING}); ++ // CraftBukkit - decompile error ++ protected static final ImmutableList> MEMORY_TYPES = ImmutableList.>of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT, new MemoryModuleType[]{MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.PLAY_DEAD_TICKS, MemoryModuleType.NEAREST_ATTACKABLE, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HAS_HUNTING_COOLDOWN, MemoryModuleType.IS_PANICKING}); + private static final EntityDataAccessor DATA_VARIANT = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_PLAYING_DEAD = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor FROM_BUCKET = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.BOOLEAN); +@@ -210,7 +217,7 @@ + + @Override + public int getMaxAirSupply() { +- return 6000; ++ return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + } + + @Override +@@ -414,10 +421,10 @@ + int i = mobeffect != null ? mobeffect.getDuration() : 0; + int j = Math.min(2400, 100 + i); + +- player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this); ++ player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // CraftBukkit + } + +- player.removeEffect(MobEffects.DIG_SLOWDOWN); ++ player.removeEffect(MobEffects.DIG_SLOWDOWN, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // Paper - Add missing effect cause + } + + @Override +@@ -464,7 +471,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch new file mode 100644 index 0000000000..e2b9543713 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/net/minecraft/world/entity/animal/camel/Camel.java +@@ -49,6 +49,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityDamageEvent; ++// CraftBukkit end + + public class Camel extends AbstractHorse { + +@@ -143,7 +146,7 @@ + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("camelBrain"); +- Brain behaviorcontroller = this.getBrain(); ++ Brain behaviorcontroller = (Brain) this.getBrain(); // CraftBukkit - decompile error + + behaviorcontroller.tick(world, this); + gameprofilerfiller.pop(); +@@ -386,13 +389,13 @@ + boolean flag = this.getHealth() < this.getMaxHealth(); + + if (flag) { +- this.heal(2.0F); ++ this.heal(2.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + } + + boolean flag1 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); + + if (flag1) { +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + + boolean flag2 = this.isBaby(); +@@ -454,9 +457,15 @@ + } + + @Override +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { ++ // CraftBukkit start - void -> boolean ++ public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { ++ boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event); ++ if (!damageResult) { ++ return false; ++ } ++ // CraftBukkit end + this.standUpInstantly(); +- super.actuallyHurt(world, source, amount); ++ return true; // CraftBukkit + } + + @Override +@@ -563,7 +572,7 @@ + } + + public void sitDown() { +- if (!this.isCamelSitting()) { ++ if (!this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), true).callEvent()) { // Paper - Add EntityToggleSitEvent + this.makeSound(SoundEvents.CAMEL_SIT); + this.setPose(Pose.SITTING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -572,7 +581,7 @@ + } + + public void standUp() { +- if (this.isCamelSitting()) { ++ if (this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) { // Paper - Add EntityToggleSitEvent + this.makeSound(SoundEvents.CAMEL_STAND); + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -581,6 +590,7 @@ + } + + public void standUpInstantly() { ++ if (this.isCamelSitting() && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); + this.resetLastPoseChangeTickToFullStand(this.level().getGameTime()); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch new file mode 100644 index 0000000000..f849bb1ffc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -270,7 +270,12 @@ + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { +- this.finalizeSpawnChildFromBreeding(world, other, null); ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ ++ this.finalizeSpawnChildFromBreeding(world, other, null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount ++ // Paper end - Add EntityFertilizeEggEvent event + this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch new file mode 100644 index 0000000000..354d8a45c5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/entity/animal/frog/ShootTongue.java ++++ b/net/minecraft/world/entity/animal/frog/ShootTongue.java +@@ -19,6 +19,9 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class ShootTongue extends Behavior { + +@@ -64,7 +67,7 @@ + + BehaviorUtils.lookAtEntity(frog, entityliving); + frog.setTongueTarget(entityliving); +- frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(entityliving.position(), 2.0F, 0))); ++ frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(entityliving.position(), 2.0F, 0))); // CraftBukkit - decompile error + this.calculatePathCounter = 10; + this.state = ShootTongue.State.MOVE_TO_TARGET; + } +@@ -85,7 +88,7 @@ + if (entity.isAlive()) { + frog.doHurtTarget(world, entity); + if (!entity.isAlive()) { +- entity.remove(Entity.RemovalReason.KILLED); ++ entity.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } + } + } +@@ -106,7 +109,7 @@ + this.eatAnimationTimer = 0; + this.state = ShootTongue.State.CATCH_ANIMATION; + } else if (this.calculatePathCounter <= 0) { +- frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(entityliving.position(), 2.0F, 0))); ++ frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(entityliving.position(), 2.0F, 0))); // CraftBukkit - decompile error + this.calculatePathCounter = 10; + } else { + --this.calculatePathCounter; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch new file mode 100644 index 0000000000..636405ea80 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch @@ -0,0 +1,87 @@ +--- a/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -50,6 +50,7 @@ + public int age; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); + protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); ++ public boolean ageLocked; // Paper + + public Tadpole(EntityType type, Level world) { + super(type, world); +@@ -74,7 +75,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -102,7 +103,7 @@ + @Override + public void aiStep() { + super.aiStep(); +- if (!this.level().isClientSide) { ++ if (!this.level().isClientSide && !this.ageLocked) { // Paper + this.setAge(this.age + 1); + } + +@@ -112,12 +113,14 @@ + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); + nbt.putInt("Age", this.age); ++ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper + } + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.setAge(nbt.getInt("Age")); ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Nullable +@@ -169,6 +172,7 @@ + Bucketable.saveDefaultDataToBucketTag(this, stack); + CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, (nbttagcompound) -> { + nbttagcompound.putInt("Age", this.getAge()); ++ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper + }); + } + +@@ -179,6 +183,7 @@ + this.setAge(nbt.getInt("Age")); + } + ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Override +@@ -210,6 +215,7 @@ + } + + private void ageUp(int seconds) { ++ if (this.ageLocked) return; // Paper + this.setAge(this.age + seconds * 20); + } + +@@ -225,12 +231,17 @@ + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +- this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), (frog) -> { ++ Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), (frog) -> { // CraftBukkit + frog.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(frog.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); + frog.setPersistenceRequired(); + frog.fudgePositionAfterSizeChange(this.getDimensions(this.getPose())); + this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F); +- }); ++ // CraftBukkit start ++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); ++ if (converted == null) { ++ this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled ++ } ++ // CraftBukkit end + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch new file mode 100644 index 0000000000..44290161de --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/net/minecraft/world/entity/animal/goat/Goat.java +@@ -53,6 +53,11 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.player.PlayerBucketFillEvent; ++// CraftBukkit end + + public class Goat extends Animal { + +@@ -184,7 +189,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -229,15 +234,24 @@ + ItemStack itemstack = player.getItemInHand(hand); + + if (itemstack.is(Items.BUCKET) && !this.isBaby()) { ++ // CraftBukkit start - Got milk? ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); ++ ++ if (event.isCancelled()) { ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + player.playSound(this.getMilkingSound(), 1.0F, 1.0F); +- ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance()); ++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit + + player.setItemInHand(hand, itemstack1); + return InteractionResult.SUCCESS; + } else { ++ boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739) + InteractionResult enuminteractionresult = super.mobInteract(player, hand); + +- if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) { ++ if (enuminteractionresult.consumesAction() && isFood) { // Paper + this.playEatingSound(); + } + +@@ -353,8 +367,7 @@ + double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); + ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); + +- this.level().addFreshEntity(entityitem); +- return true; ++ return this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem) != null; // Paper - Call EntityDropItemEvent + } + } + +@@ -383,4 +396,15 @@ + public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { + return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); + } ++ ++ // Paper start - Goat ram API ++ public void ram(net.minecraft.world.entity.LivingEntity entity) { ++ Brain brain = this.getBrain(); ++ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); ++ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); ++ brain.eraseMemory(MemoryModuleType.BREED_TARGET); ++ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); ++ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM); ++ } ++ // Paper end - Goat ram API + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch new file mode 100644 index 0000000000..78095235f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java ++++ b/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +@@ -69,9 +69,16 @@ + super.dropEquipment(world); + if (this.hasChest()) { + this.spawnAtLocation(world, Blocks.CHEST); ++ //this.setChest(false); // Paper - moved to post death logic ++ } ++ } ++ // Paper start ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { ++ if (this.hasChest() && (event == null || !event.isCancelled())) { + this.setChest(false); + } + } ++ // Paper end + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch new file mode 100644 index 0000000000..e975349f91 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch @@ -0,0 +1,203 @@ +--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -79,6 +79,17 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.ticks.ContainerSingleItem; + ++// CraftBukkit start ++import java.util.Arrays; ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end ++ + public abstract class AbstractHorse extends Animal implements ContainerListener, HasCustomInventoryScreen, OwnableEntity, PlayerRideableJumping, Saddleable { + + public static final int EQUIPMENT_SLOT_OFFSET = 400; +@@ -166,8 +177,54 @@ + @Override + public boolean stillValid(Player player) { + return player.getVehicle() == AbstractHorse.this || player.canInteractWithEntity((Entity) AbstractHorse.this, 4.0D); ++ } ++ ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ @Override ++ public List getContents() { ++ return Arrays.asList(this.getTheItem()); ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public InventoryHolder getOwner() { ++ return (org.bukkit.entity.AbstractHorse) AbstractHorse.this.getBukkitEntity(); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return AbstractHorse.this.getBukkitEntity().getLocation(); + } ++ // CraftBukkit end + }; ++ public int maxDomestication = 100; // CraftBukkit - store max domestication value + + protected AbstractHorse(EntityType type, Level world) { + super(type, world); +@@ -312,7 +369,7 @@ + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return !this.isVehicle(); + } + +@@ -366,7 +423,7 @@ + public void createInventory() { + SimpleContainer inventorysubcontainer = this.inventory; + +- this.inventory = new SimpleContainer(this.getInventorySize()); ++ this.inventory = new SimpleContainer(this.getInventorySize(), (org.bukkit.entity.AbstractHorse) this.getBukkitEntity()); // CraftBukkit + if (inventorysubcontainer != null) { + inventorysubcontainer.removeListener(this); + int i = Math.min(inventorysubcontainer.getContainerSize(), this.inventory.getContainerSize()); +@@ -470,7 +527,7 @@ + } + + public int getMaxTemper() { +- return 100; ++ return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100 + } + + @Override +@@ -528,7 +585,7 @@ + b0 = 5; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) { + f = 10.0F; +@@ -536,12 +593,12 @@ + b0 = 10; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } + + if (this.getHealth() < this.getMaxHealth() && f > 0.0F) { +- this.heal(f); ++ this.heal(f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit + flag = true; + } + +@@ -618,7 +675,7 @@ + if (world instanceof ServerLevel worldserver) { + if (this.isAlive()) { + if (this.random.nextInt(900) == 0 && this.deathTime == 0) { +- this.heal(1.0F); ++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + } + + if (this.canEatGrass()) { +@@ -720,7 +777,16 @@ + } + } + ++ } ++ ++ // Paper start - Horse API ++ public void setMouthOpen(boolean open) { ++ this.setFlag(FLAG_OPEN_MOUTH, open); ++ } ++ public boolean isMouthOpen() { ++ return this.getFlag(FLAG_OPEN_MOUTH); + } ++ // Paper end - Horse API + + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { +@@ -764,6 +830,11 @@ + this.setFlag(16, eatingGrass); + } + ++ // Paper start - Horse API ++ public void setForceStanding(boolean standing) { ++ this.setFlag(FLAG_STANDING, standing); ++ } ++ // Paper end - Horse API + public void setStanding(boolean angry) { + if (angry) { + this.setEating(false); +@@ -883,6 +954,7 @@ + if (this.getOwnerUUID() != null) { + nbt.putUUID("Owner", this.getOwnerUUID()); + } ++ nbt.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit + + if (!this.inventory.getItem(0).isEmpty()) { + nbt.put("SaddleItem", this.inventory.getItem(0).save(this.registryAccess())); +@@ -909,7 +981,12 @@ + + if (uuid != null) { + this.setOwnerUUID(uuid); ++ } ++ // CraftBukkit start ++ if (nbt.contains("Bukkit.MaxDomestication")) { ++ this.maxDomestication = nbt.getInt("Bukkit.MaxDomestication"); + } ++ // CraftBukkit end + + if (nbt.contains("SaddleItem", 10)) { + ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("SaddleItem")).orElse(ItemStack.EMPTY); +@@ -1012,6 +1089,17 @@ + + @Override + public void handleStartJump(int height) { ++ // CraftBukkit start ++ float power; ++ if (height >= 90) { ++ power = 1.0F; ++ } else { ++ power = 0.4F + 0.4F * (float) height / 90.0F; ++ } ++ if (!CraftEventFactory.callHorseJumpEvent(this, power)) { ++ return; ++ } ++ // CraftBukkit end + this.allowStandSliding = true; + this.standIfPossible(); + this.playJumpSound(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch new file mode 100644 index 0000000000..df44c55d01 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/net/minecraft/world/entity/animal/horse/Llama.java +@@ -71,17 +71,23 @@ + @Nullable + private Llama caravanHead; + @Nullable +- private Llama caravanTail; ++ public Llama caravanTail; // Paper + + public Llama(EntityType type, Level world) { + super(type, world); + this.getNavigation().setRequiredPathLength(40.0F); ++ this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value + } + + public boolean isTraderLlama() { + return false; + } + ++ // CraftBukkit start ++ public void setStrengthPublic(int i) { ++ this.setStrength(i); ++ } ++ // CraftBukkit end + private void setStrength(int strength) { + this.entityData.set(Llama.DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength))); + } +@@ -171,12 +177,12 @@ + f = 10.0F; + if (this.isTamed() && this.getAge() == 0 && this.canFallInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } + + if (this.getHealth() < this.getMaxHealth() && f > 0.0F) { +- this.heal(f); ++ this.heal(f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + flag = true; + } + +@@ -289,7 +295,7 @@ + + @Override + public int getMaxTemper() { +- return 30; ++ return super.getMaxTemper(); // Paper - Missing entity API; delegate to parent + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch new file mode 100644 index 0000000000..a0ae868b36 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/animal/horse/SkeletonHorse.java ++++ b/net/minecraft/world/entity/animal/horse/SkeletonHorse.java +@@ -26,6 +26,9 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class SkeletonHorse extends AbstractHorse { + +@@ -122,7 +125,7 @@ + public void aiStep() { + super.aiStep(); + if (this.isTrap() && this.trapTime++ >= 18000) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch new file mode 100644 index 0000000000..8f22bd7fd2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java ++++ b/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +@@ -20,6 +20,7 @@ + public class SkeletonTrapGoal extends Goal { + + private final SkeletonHorse horse; ++ private java.util.List eligiblePlayers; // Paper + + public SkeletonTrapGoal(SkeletonHorse skeletonHorse) { + this.horse = skeletonHorse; +@@ -27,12 +28,13 @@ + + @Override + public boolean canUse() { +- return this.horse.level().hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); ++ return !(eligiblePlayers = this.horse.level().findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D, net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING)).isEmpty(); // Paper - Affects Spawning API & SkeletonHorseTrapEvent + } + + @Override + public void tick() { + ServerLevel worldserver = (ServerLevel) this.horse.level(); ++ if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper + DifficultyInstance difficultydamagescaler = worldserver.getCurrentDifficultyAt(this.horse.blockPosition()); + + this.horse.setTrap(false); +@@ -43,12 +45,12 @@ + if (entitylightning != null) { + entitylightning.moveTo(this.horse.getX(), this.horse.getY(), this.horse.getZ()); + entitylightning.setVisualOnly(true); +- worldserver.addFreshEntity(entitylightning); ++ worldserver.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRAP); // CraftBukkit + Skeleton entityskeleton = this.createSkeleton(difficultydamagescaler, this.horse); + + if (entityskeleton != null) { + entityskeleton.startRiding(this.horse); +- worldserver.addFreshEntityWithPassengers(entityskeleton); ++ worldserver.addFreshEntityWithPassengers(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRAP); // CraftBukkit + + for (int i = 0; i < 3; ++i) { + AbstractHorse entityhorseabstract = this.createHorse(difficultydamagescaler); +@@ -59,7 +61,7 @@ + if (entityskeleton1 != null) { + entityskeleton1.startRiding(entityhorseabstract); + entityhorseabstract.push(this.horse.getRandom().triangle(0.0D, 1.1485D), 0.0D, this.horse.getRandom().triangle(0.0D, 1.1485D)); +- worldserver.addFreshEntityWithPassengers(entityhorseabstract); ++ worldserver.addFreshEntityWithPassengers(entityhorseabstract, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch new file mode 100644 index 0000000000..1edf3cd1bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/entity/animal/horse/TraderLlama.java ++++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java +@@ -21,6 +21,9 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.ServerLevelAccessor; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class TraderLlama extends Llama { + +@@ -94,7 +97,7 @@ + this.despawnDelay = this.isLeashedToWanderingTrader() ? ((WanderingTrader) this.getLeashHolder()).getDespawnDelay() - 1 : this.despawnDelay - 1; + if (this.despawnDelay <= 0) { + this.removeLeash(); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -160,7 +163,7 @@ + + @Override + public void start() { +- this.mob.setTarget(this.ownerLastHurtBy); ++ this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit + Entity entity = this.llama.getLeashHolder(); + + if (entity instanceof WanderingTrader) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch new file mode 100644 index 0000000000..a6567df470 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch @@ -0,0 +1,56 @@ +--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -267,6 +267,13 @@ + this.dropFromGiftLootTable(worldserver, BuiltInLootTables.SNIFFER_DIGGING, (worldserver1, itemstack) -> { + ItemEntity entityitem = new ItemEntity(this.level(), (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack); + ++ // CraftBukkit start - handle EntityDropItemEvent ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + entityitem.setDefaultPickUpDelay(); + worldserver1.addFreshEntity(entityitem); + }); +@@ -308,7 +315,7 @@ + List list = (List) this.getExploredPositions().limit(20L).collect(Collectors.toList()); + + list.add(0, GlobalPos.of(this.level().dimension(), pos)); +- this.getBrain().setMemory(MemoryModuleType.SNIFFER_EXPLORED_POSITIONS, (Object) list); ++ this.getBrain().setMemory(MemoryModuleType.SNIFFER_EXPLORED_POSITIONS, list); // CraftBukkit - decompile error + return this; + } + +@@ -333,13 +340,19 @@ + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event ++ + ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG); + ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack); + + entityitem.setDefaultPickUpDelay(); +- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); ++ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event ++ if (this.spawnAtLocation(world, entityitem) != null) { // Paper - Call EntityDropItemEvent + this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); +- world.addFreshEntity(entityitem); ++ } // Paper - Call EntityDropItemEvent + } + + @Override +@@ -444,7 +457,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch new file mode 100644 index 0000000000..9f6803d4ee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch @@ -0,0 +1,91 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -19,12 +19,18 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.BaseFireBlock; + import net.minecraft.world.level.dimension.end.EndDragonFight; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end + + public class EndCrystal extends Entity { + + private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); + private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); + public int time; ++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + + public EndCrystal(EntityType type, Level world) { + super(type, world); +@@ -57,8 +63,23 @@ + BlockPos blockposition = this.blockPosition(); + + if (((ServerLevel) this.level()).getDragonFight() != null && this.level().getBlockState(blockposition).isAir()) { +- this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition)); ++ // CraftBukkit start ++ if (!CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { ++ this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition)); ++ } ++ // CraftBukkit end + } ++ // Paper start - Fix invulnerable end crystals ++ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { ++ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld()) ++ || ((ServerLevel) this.level()).getDragonFight() == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { ++ this.setInvulnerable(false); ++ this.setBeamTarget(null); ++ } ++ } ++ // Paper end - Fix invulnerable end crystals + } + + } +@@ -70,6 +91,7 @@ + } + + nbt.putBoolean("ShowBottom", this.showsBottom()); ++ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -78,6 +100,7 @@ + if (nbt.contains("ShowBottom", 1)) { + this.setShowBottom(nbt.getBoolean("ShowBottom")); + } ++ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals + + } + +@@ -99,12 +122,26 @@ + return false; + } else { + if (!this.isRemoved()) { +- this.remove(Entity.RemovalReason.KILLED); ++ // CraftBukkit start - All non-living entities need this ++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) { ++ return false; ++ } ++ // CraftBukkit end + if (!source.is(DamageTypeTags.IS_EXPLOSION)) { + DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; + +- world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK); ++ // CraftBukkit start ++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause ++ world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); ++ } else { ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } ++ // CraftBukkit end + + this.onDestroyedBy(world, source); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch new file mode 100644 index 0000000000..01ecc5bc3d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch @@ -0,0 +1,331 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -37,20 +37,35 @@ + import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhaseManager; + import net.minecraft.world.entity.monster.Enemy; + import net.minecraft.world.entity.player.Player; +-import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; +-import net.minecraft.world.level.block.state.BlockState; +-import net.minecraft.world.level.dimension.end.EndDragonFight; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.levelgen.Heightmap; + import net.minecraft.world.level.levelgen.feature.EndPodiumFeature; + import net.minecraft.world.level.pathfinder.BinaryHeap; + import net.minecraft.world.level.pathfinder.Node; + import net.minecraft.world.level.pathfinder.Path; ++import org.slf4j.Logger; ++ ++// CraftBukkit start ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.enchantment.EnchantmentHelper; ++import net.minecraft.world.level.Explosion; ++import net.minecraft.world.level.ServerExplosion; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.dimension.end.EndDragonFight; ++import net.minecraft.world.level.storage.loot.LootParams; ++import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; +-import org.slf4j.Logger; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.entity.EntityExplodeEvent; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class EnderDragon extends Mob implements Enemy { + +@@ -88,6 +103,11 @@ + private final Node[] nodes; + private final int[] nodeAdjacency; + private final BinaryHeap openSet; ++ private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ // Paper start - Allow changing the EnderDragon podium ++ @Nullable ++ private BlockPos podium; ++ // Paper end - Allow changing the EnderDragon podium + + public EnderDragon(EntityType entitytypes, Level world) { + super(EntityType.ENDER_DRAGON, world); +@@ -108,6 +128,7 @@ + this.setHealth(this.getMaxHealth()); + this.noPhysics = true; + this.phaseManager = new EnderDragonPhaseManager(this); ++ this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit + } + + public void setDragonFight(EndDragonFight fight) { +@@ -124,7 +145,20 @@ + + public static AttributeSupplier.Builder createAttributes() { + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); ++ } ++ ++ // Paper start - Allow changing the EnderDragon podium ++ public BlockPos getPodium() { ++ if (this.podium == null) { ++ return EndPodiumFeature.getLocation(this.getFightOrigin()); ++ } ++ return this.podium; ++ } ++ ++ public void setPodium(@Nullable BlockPos blockPos) { ++ this.podium = blockPos; + } ++ // Paper end - Allow changing the EnderDragon podium + + @Override + public boolean isFlapping() { +@@ -218,7 +252,7 @@ + + Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation(); + +- if (vec3d1 != null) { ++ if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering + double d0 = vec3d1.x - this.getX(); + double d1 = vec3d1.y - this.getY(); + double d2 = vec3d1.z - this.getZ(); +@@ -379,7 +413,14 @@ + if (this.nearestCrystal.isRemoved()) { + this.nearestCrystal = null; + } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) { +- this.setHealth(this.getHealth() + 1.0F); ++ // CraftBukkit start ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setHealth((float) (this.getHealth() + event.getAmount())); ++ } ++ // CraftBukkit end + } + } + +@@ -417,7 +458,7 @@ + double d3 = entity.getZ() - d1; + double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); + +- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); ++ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + if (!this.phaseManager.getCurrentPhase().isSitting() && entityliving.getLastHurtByMobTimestamp() < entity.tickCount - 2) { + DamageSource damagesource = this.damageSources().mobAttack(this); + +@@ -458,6 +499,9 @@ + int j1 = Mth.floor(box.maxZ); + boolean flag = false; + boolean flag1 = false; ++ // CraftBukkit start - Create a list to hold all the destroyed blocks ++ List destroyedBlocks = new java.util.ArrayList(); ++ // CraftBukkit end + + for (int k1 = i; k1 <= l; ++k1) { + for (int l1 = j; l1 <= i1; ++l1) { +@@ -467,14 +511,66 @@ + + if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { + if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { +- flag1 = world.removeBlock(blockposition, false) || flag1; ++ // CraftBukkit start - Add blocks to list rather than destroying them ++ // flag1 = worldserver.removeBlock(blockposition, false) || flag1; ++ flag1 = true; ++ destroyedBlocks.add(CraftBlock.at(world, blockposition)); ++ // CraftBukkit end + } else { + flag = true; + } + } ++ } ++ } ++ } ++ ++ // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks ++ // SPIGOT-4882: don't fire event if nothing hit ++ if (!flag1) { ++ return flag; ++ } ++ ++ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this, destroyedBlocks, 0F, this.explosionSource.getBlockInteraction()); ++ 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.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false); ++ } ++ } else { ++ for (org.bukkit.block.Block block : event.blockList()) { ++ org.bukkit.Material blockId = block.getType(); ++ if (blockId.isAir()) { ++ continue; ++ } ++ ++ CraftBlock craftBlock = ((CraftBlock) block); ++ BlockPos blockposition = craftBlock.getPosition(); ++ ++ Block nmsBlock = craftBlock.getNMS().getBlock(); ++ if (nmsBlock.dropFromExplosion(this.explosionSource)) { ++ BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null; ++ LootParams.Builder loottableinfo_builder = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity); ++ ++ craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> { ++ Block.popResource(this.level(), blockposition, itemstack); ++ }); ++ craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false); + } ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent()) ++ continue; ++ // Paper end - TNTPrimeEvent ++ nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource); ++ ++ this.level().removeBlock(blockposition, false); + } + } ++ // CraftBukkit end + + if (flag1) { + BlockPos blockposition1 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(i1 - j + 1), k + this.random.nextInt(j1 - k + 1)); +@@ -531,7 +627,15 @@ + + @Override + public void kill(ServerLevel world) { +- this.remove(Entity.RemovalReason.KILLED); ++ // Paper start - Fire entity death event ++ this.silentDeath = true; ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill()); ++ if (deathEvent.isCancelled()) { ++ this.silentDeath = false; // Reset to default if event was cancelled ++ return; ++ } ++ // Paper end - Fire entity death event ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + this.gameEvent(GameEvent.ENTITY_DIE); + if (this.dragonFight != null) { + this.dragonFight.updateDragon(this); +@@ -540,7 +644,22 @@ + + } + ++ // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time. + @Override ++ public int getExpReward(ServerLevel worldserver, Entity entity) { ++ // CraftBukkit - Moved from #tickDeath method ++ boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); ++ short short0 = 500; ++ ++ if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { ++ short0 = 12000; ++ } ++ ++ return flag ? short0 : 0; ++ } ++ // CraftBukkit end ++ ++ @Override + protected void tickDeath() { + if (this.dragonFight != null) { + this.dragonFight.updateDragon(this); +@@ -555,21 +674,44 @@ + this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double) f, this.getY() + 2.0D + (double) f1, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D); + } + ++ // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method ++ /* + short short0 = 500; + + if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { + short0 = 12000; + } ++ */ ++ int short0 = this.expToDrop; ++ // CraftBukkit end + + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +- if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F)); ++ if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp ++ ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonDeathTime == 1 && !this.isSilent()) { +- worldserver.globalLevelEvent(1028, this.blockPosition(), 0); ++ // CraftBukkit start - Use relative location for far away sounds ++ // worldserver.globalLevelEvent(1028, this.blockPosition(), 0); ++ int viewDistance = worldserver.getCraftServer().getViewDistance() * 16; ++ for (net.minecraft.server.level.ServerPlayer player : worldserver.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ double deltaX = this.getX() - player.getX(); ++ double deltaZ = this.getZ() - player.getZ(); ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ final double soundRadiusSquared = worldserver.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule ++ if ( !worldserver.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance; ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true)); ++ } else { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true)); ++ } ++ } ++ // CraftBukkit end + } + } + +@@ -592,15 +734,15 @@ + if (world1 instanceof ServerLevel) { + ServerLevel worldserver1 = (ServerLevel) world1; + +- if (worldserver1.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F)); ++ if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp ++ ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonFight != null) { + this.dragonFight.setDragonKilled(this); + } + +- this.remove(Entity.RemovalReason.KILLED); ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + this.gameEvent(GameEvent.ENTITY_DIE); + } + } +@@ -814,6 +956,7 @@ + super.addAdditionalSaveData(nbt); + nbt.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId()); + nbt.putInt("DragonDeathTime", this.dragonDeathTime); ++ nbt.putInt("Bukkit.expToDrop", this.expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts + } + + @Override +@@ -827,6 +970,11 @@ + this.dragonDeathTime = nbt.getInt("DragonDeathTime"); + } + ++ // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts ++ if (nbt.contains("Bukkit.expToDrop")) { ++ this.expToDrop = nbt.getInt("Bukkit.expToDrop"); ++ } ++ // CraftBukkit end + } + + @Override +@@ -879,7 +1027,7 @@ + vec3d = this.getViewVector(tickDelta); + } + } else { +- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); ++ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium + + f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); + float f3 = 6.0F / f1; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch new file mode 100644 index 0000000000..2dcf3ac15b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +@@ -42,7 +42,7 @@ + public void doServerTick(ServerLevel world) { + this.time++; + if (this.targetLocation == null) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + this.targetLocation = Vec3.atBottomCenterOf(blockPos); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch new file mode 100644 index 0000000000..f07b2b70e3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +@@ -53,7 +53,7 @@ + + private void findNewTarget(ServerLevel world) { + if (this.currentPath != null && this.currentPath.isDone()) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); + if (this.dragon.getRandom().nextInt(i + 3) == 0) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch new file mode 100644 index 0000000000..ce24273215 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +@@ -52,7 +52,7 @@ + private void findNewTarget(ServerLevel world) { + if (this.currentPath == null || this.currentPath.isDone()) { + int i = this.dragon.findClosestNode(); +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + Player player = world.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); + int j; + if (player != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch new file mode 100644 index 0000000000..0d9035a506 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +@@ -42,7 +42,7 @@ + public void doServerTick(ServerLevel world) { + if (this.targetLocation == null) { + this.targetLocation = Vec3.atBottomCenterOf( +- world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())) ++ world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium + ); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch new file mode 100644 index 0000000000..9477328964 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +@@ -10,6 +10,9 @@ + import net.minecraft.world.entity.AreaEffectCloud; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { + +@@ -86,7 +89,13 @@ + this.flame.setDuration(200); + this.flame.setParticle(ParticleTypes.DRAGON_BREATH); + this.flame.addEffect(new MobEffectInstance(MobEffects.HARM)); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + world.addFreshEntity(this.flame); ++ // Paper start - EnderDragon Events ++ } else { ++ this.end(); ++ } ++ // Paper end - EnderDragon Events + } + + } +@@ -100,7 +109,7 @@ + @Override + public void end() { + if (this.flame != null) { +- this.flame.discard(); ++ this.flame.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + this.flame = null; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch new file mode 100644 index 0000000000..7a5a567fd1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +@@ -79,8 +79,11 @@ + } + + DragonFireball dragonFireball = new DragonFireball(world, this.dragon, vec34.normalize()); ++ dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported + dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events + world.addFreshEntity(dragonFireball); ++ else dragonFireball.discard(null); // Paper - EnderDragon Events + this.fireballCharge = 0; + if (this.currentPath != null) { + while (!this.currentPath.isDone()) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch new file mode 100644 index 0000000000..54fe962df5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +@@ -24,7 +24,7 @@ + @Override + public void doServerTick(ServerLevel world) { + if (!this.firstTick && this.currentPath != null) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch new file mode 100644 index 0000000000..d39507da58 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java +@@ -5,6 +5,11 @@ + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftEnderDragon; ++import org.bukkit.event.entity.EnderDragonChangePhaseEvent; ++// CraftBukkit end ++ + public class EnderDragonPhaseManager { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -24,6 +29,19 @@ + this.currentPhase.end(); + } + ++ // CraftBukkit start - Call EnderDragonChangePhaseEvent ++ EnderDragonChangePhaseEvent event = new EnderDragonChangePhaseEvent( ++ (CraftEnderDragon) this.dragon.getBukkitEntity(), ++ (this.currentPhase == null) ? null : CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()), ++ CraftEnderDragon.getBukkitPhase(type) ++ ); ++ this.dragon.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ type = CraftEnderDragon.getMinecraftPhase(event.getNewPhase()); ++ // CraftBukkit end ++ + this.currentPhase = this.getPhase(type); + if (!this.dragon.level().isClientSide) { + this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, type.getId()); +@@ -45,6 +63,6 @@ + this.phases[i] = type.createInstance(this.dragon); + } + +- return this.phases[i]; ++ return (T) this.phases[i]; // CraftBukkit - decompile error + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch new file mode 100644 index 0000000000..67144bc046 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch @@ -0,0 +1,165 @@ +--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -10,14 +10,10 @@ + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; ++import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.server.level.ServerBossEvent; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.sounds.SoundEvent; +-import net.minecraft.sounds.SoundEvents; + import net.minecraft.tags.BlockTags; + import net.minecraft.tags.DamageTypeTags; + import net.minecraft.tags.EntityTypeTags; +@@ -54,8 +50,21 @@ + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerBossEvent; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundEvents; ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRegainHealthEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end + + public class WitherBoss extends Monster implements RangedAttackMob { + +@@ -77,7 +86,12 @@ + return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable(); + }; + private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); ++ // Paper start ++ private boolean canPortal = false; + ++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } ++ // Paper end ++ + public WitherBoss(EntityType type, Level world) { + super(type, world); + this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true); +@@ -252,15 +266,42 @@ + i = this.getInvulnerableTicks() - 1; + this.bossEvent.setProgress(1.0F - (float) i / 220.0F); + if (i <= 0) { +- world.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, Level.ExplosionInteraction.MOB); ++ // CraftBukkit start ++ // worldserver.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, World.a.MOB); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ world.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); ++ } ++ // CraftBukkit end ++ + if (!this.isSilent()) { +- world.globalLevelEvent(1023, this.blockPosition(), 0); ++ // CraftBukkit start - Use relative location for far away sounds ++ // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0); ++ int viewDistance = world.getCraftServer().getViewDistance() * 16; ++ for (ServerPlayer player : world.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ double deltaX = this.getX() - player.getX(); ++ double deltaZ = this.getZ() - player.getZ(); ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ final double soundRadiusSquared = world.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule ++ if ( !world.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance; ++ player.connection.send(new ClientboundLevelEventPacket(1023, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true)); ++ } else { ++ player.connection.send(new ClientboundLevelEventPacket(1023, this.blockPosition(), 0, true)); ++ } ++ } ++ // CraftBukkit end + } + } + + this.setInvulnerableTicks(i); + if (this.tickCount % 10 == 0) { +- this.heal(10.0F); ++ this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit + } + + } else { +@@ -305,6 +346,7 @@ + if (!list.isEmpty()) { + LivingEntity entityliving1 = (LivingEntity) list.get(this.random.nextInt(list.size())); + ++ if (CraftEventFactory.callEntityTargetLivingEvent(this, entityliving1, EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit + this.setAlternativeTarget(i, entityliving1.getId()); + } + } +@@ -331,6 +373,11 @@ + BlockState iblockdata = world.getBlockState(blockposition); + + if (WitherBoss.canDestroy(iblockdata)) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ continue; ++ } ++ // CraftBukkit end + flag = world.destroyBlock(blockposition, true, this) || flag; + } + } +@@ -342,7 +389,7 @@ + } + + if (this.tickCount % 20 == 0) { +- this.heal(1.0F); ++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + } + + this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); +@@ -488,10 +535,10 @@ + @Override + protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { + super.dropCustomDeathLoot(world, source, causedByPlayer); +- ItemEntity entityitem = this.spawnAtLocation(world, (ItemLike) Items.NETHER_STAR); ++ ItemEntity entityitem = this.spawnAtLocation(world, new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer + + if (entityitem != null) { +- entityitem.setExtendedLifetime(); ++ entityitem.setExtendedLifetime(); // Paper - diff on change + } + + } +@@ -499,7 +546,7 @@ + @Override + public void checkDespawn() { + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + this.noActionTime = 0; + } +@@ -549,12 +596,12 @@ + + @Override + public boolean canUsePortal(boolean allowVehicles) { +- return false; ++ return this.canPortal; // Paper + } + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + private class WitherDoNothingGoal extends Goal { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch new file mode 100644 index 0000000000..edb69ea66e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch @@ -0,0 +1,513 @@ +--- a/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -25,7 +25,6 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityDimensions; + import net.minecraft.world.entity.EntityType; +-import net.minecraft.world.entity.EquipmentSlot; + import net.minecraft.world.entity.HumanoidArm; + import net.minecraft.world.entity.LightningBolt; + import net.minecraft.world.entity.LivingEntity; +@@ -33,7 +32,6 @@ + import net.minecraft.world.entity.Pose; + import net.minecraft.world.entity.ai.attributes.AttributeSupplier; + import net.minecraft.world.entity.ai.attributes.Attributes; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.vehicle.AbstractMinecart; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; +@@ -47,6 +45,14 @@ + import net.minecraft.world.level.material.PushReaction; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.inventory.EquipmentSlot; ++import org.bukkit.craftbukkit.CraftEquipmentSlot; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerArmorStandManipulateEvent; ++// CraftBukkit end + + public class ArmorStand extends LivingEntity { + +@@ -101,9 +107,17 @@ + public Rotations rightArmPose; + public Rotations leftLegPose; + public Rotations rightLegPose; ++ public boolean canMove = true; // Paper ++ // Paper start - Allow ArmorStands not to tick ++ public boolean canTick = true; ++ public boolean canTickSetByAPI = false; ++ private boolean noTickPoseDirty = false; ++ private boolean noTickEquipmentDirty = false; ++ // Paper end - Allow ArmorStands not to tick + + public ArmorStand(EntityType type, Level world) { + super(type, world); ++ if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick + this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); + this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); + this.headPose = ArmorStand.DEFAULT_HEAD_POSE; +@@ -123,7 +137,14 @@ + return createLivingAttributes().add(Attributes.STEP_HEIGHT, 0.0D); + } + ++ // CraftBukkit start - SPIGOT-3607, SPIGOT-3637 + @Override ++ public float getBukkitYaw() { ++ return this.getYRot(); ++ } ++ // CraftBukkit end ++ ++ @Override + public void refreshDimensions() { + double d0 = this.getX(); + double d1 = this.getY(); +@@ -165,7 +186,7 @@ + } + + @Override +- public ItemStack getItemBySlot(EquipmentSlot slot) { ++ public ItemStack getItemBySlot(net.minecraft.world.entity.EquipmentSlot slot) { + switch (slot.getType()) { + case HAND: + return (ItemStack) this.handItems.get(slot.getIndex()); +@@ -177,21 +198,29 @@ + } + + @Override +- public boolean canUseSlot(EquipmentSlot slot) { +- return slot != EquipmentSlot.BODY && !this.isDisabled(slot); ++ public boolean canUseSlot(net.minecraft.world.entity.EquipmentSlot slot) { ++ return slot != net.minecraft.world.entity.EquipmentSlot.BODY && !this.isDisabled(slot); ++ } ++ ++ @Override ++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack) { ++ // CraftBukkit start ++ this.setItemSlot(slot, stack, false); + } + + @Override +- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +- this.verifyEquippedItem(stack); +- switch (slot.getType()) { ++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) { ++ // CraftBukkit end ++ this.verifyEquippedItem(itemstack); ++ switch (enumitemslot.getType()) { + case HAND: +- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(enumitemslot, (ItemStack) this.handItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit + break; + case HUMANOID_ARMOR: +- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit + } + ++ this.noTickEquipmentDirty = true; // Paper - Allow ArmorStands not to tick; Still update equipment + } + + @Override +@@ -227,6 +256,7 @@ + } + + nbt.put("Pose", this.writePose()); ++ if (this.canTickSetByAPI) nbt.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick + } + + @Override +@@ -261,6 +291,12 @@ + this.setNoBasePlate(nbt.getBoolean("NoBasePlate")); + this.setMarker(nbt.getBoolean("Marker")); + this.noPhysics = !this.hasPhysics(); ++ // Paper start - Allow ArmorStands not to tick ++ if (nbt.contains("Paper.CanTickOverride")) { ++ this.canTick = nbt.getBoolean("Paper.CanTickOverride"); ++ this.canTickSetByAPI = true; ++ } ++ // Paper end - Allow ArmorStands not to tick + CompoundTag nbttagcompound2 = nbt.getCompound("Pose"); + + this.readPose(nbttagcompound2); +@@ -318,7 +354,7 @@ + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return false; + } + +@@ -327,6 +363,7 @@ + + @Override + protected void pushEntities() { ++ if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups + List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); + Iterator iterator = list.iterator(); + +@@ -341,7 +378,7 @@ + } + + @Override +- public InteractionResult interactAt(Player player, Vec3 hitPos, InteractionHand hand) { ++ public InteractionResult interactAt(net.minecraft.world.entity.player.Player player, Vec3 hitPos, InteractionHand hand) { + ItemStack itemstack = player.getItemInHand(hand); + + if (!this.isMarker() && !itemstack.is(Items.NAME_TAG)) { +@@ -350,11 +387,11 @@ + } else if (player.level().isClientSide) { + return InteractionResult.SUCCESS_SERVER; + } else { +- EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack); ++ net.minecraft.world.entity.EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack); + + if (itemstack.isEmpty()) { +- EquipmentSlot enumitemslot1 = this.getClickedSlot(hitPos); +- EquipmentSlot enumitemslot2 = this.isDisabled(enumitemslot1) ? enumitemslot : enumitemslot1; ++ net.minecraft.world.entity.EquipmentSlot enumitemslot1 = this.getClickedSlot(hitPos); ++ net.minecraft.world.entity.EquipmentSlot enumitemslot2 = this.isDisabled(enumitemslot1) ? enumitemslot : enumitemslot1; + + if (this.hasItemInSlot(enumitemslot2) && this.swapItem(player, enumitemslot2, itemstack, hand)) { + return InteractionResult.SUCCESS_SERVER; +@@ -364,7 +401,7 @@ + return InteractionResult.FAIL; + } + +- if (enumitemslot.getType() == EquipmentSlot.Type.HAND && !this.showArms()) { ++ if (enumitemslot.getType() == net.minecraft.world.entity.EquipmentSlot.Type.HAND && !this.showArms()) { + return InteractionResult.FAIL; + } + +@@ -380,39 +417,57 @@ + } + } + +- private EquipmentSlot getClickedSlot(Vec3 hitPos) { +- EquipmentSlot enumitemslot = EquipmentSlot.MAINHAND; ++ private net.minecraft.world.entity.EquipmentSlot getClickedSlot(Vec3 hitPos) { ++ net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.world.entity.EquipmentSlot.MAINHAND; + boolean flag = this.isSmall(); + double d0 = hitPos.y / (double) (this.getScale() * this.getAgeScale()); +- EquipmentSlot enumitemslot1 = EquipmentSlot.FEET; ++ net.minecraft.world.entity.EquipmentSlot enumitemslot1 = net.minecraft.world.entity.EquipmentSlot.FEET; + + if (d0 >= 0.1D && d0 < 0.1D + (flag ? 0.8D : 0.45D) && this.hasItemInSlot(enumitemslot1)) { +- enumitemslot = EquipmentSlot.FEET; +- } else if (d0 >= 0.9D + (flag ? 0.3D : 0.0D) && d0 < 0.9D + (flag ? 1.0D : 0.7D) && this.hasItemInSlot(EquipmentSlot.CHEST)) { +- enumitemslot = EquipmentSlot.CHEST; +- } else if (d0 >= 0.4D && d0 < 0.4D + (flag ? 1.0D : 0.8D) && this.hasItemInSlot(EquipmentSlot.LEGS)) { +- enumitemslot = EquipmentSlot.LEGS; +- } else if (d0 >= 1.6D && this.hasItemInSlot(EquipmentSlot.HEAD)) { +- enumitemslot = EquipmentSlot.HEAD; +- } else if (!this.hasItemInSlot(EquipmentSlot.MAINHAND) && this.hasItemInSlot(EquipmentSlot.OFFHAND)) { +- enumitemslot = EquipmentSlot.OFFHAND; ++ enumitemslot = net.minecraft.world.entity.EquipmentSlot.FEET; ++ } else if (d0 >= 0.9D + (flag ? 0.3D : 0.0D) && d0 < 0.9D + (flag ? 1.0D : 0.7D) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.CHEST)) { ++ enumitemslot = net.minecraft.world.entity.EquipmentSlot.CHEST; ++ } else if (d0 >= 0.4D && d0 < 0.4D + (flag ? 1.0D : 0.8D) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.LEGS)) { ++ enumitemslot = net.minecraft.world.entity.EquipmentSlot.LEGS; ++ } else if (d0 >= 1.6D && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.HEAD)) { ++ enumitemslot = net.minecraft.world.entity.EquipmentSlot.HEAD; ++ } else if (!this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.OFFHAND)) { ++ enumitemslot = net.minecraft.world.entity.EquipmentSlot.OFFHAND; + } + + return enumitemslot; + } + +- public boolean isDisabled(EquipmentSlot slot) { +- return (this.disabledSlots & 1 << slot.getFilterBit(0)) != 0 || slot.getType() == EquipmentSlot.Type.HAND && !this.showArms(); ++ public boolean isDisabled(net.minecraft.world.entity.EquipmentSlot slot) { ++ return (this.disabledSlots & 1 << slot.getFilterBit(0)) != 0 || slot.getType() == net.minecraft.world.entity.EquipmentSlot.Type.HAND && !this.showArms(); + } + +- private boolean swapItem(Player player, EquipmentSlot slot, ItemStack stack, InteractionHand hand) { ++ private boolean swapItem(net.minecraft.world.entity.player.Player player, net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack, InteractionHand hand) { + ItemStack itemstack1 = this.getItemBySlot(slot); + + if (!itemstack1.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(8)) != 0) { + return false; + } else if (itemstack1.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(16)) != 0) { + return false; +- } else if (player.hasInfiniteMaterials() && itemstack1.isEmpty() && !stack.isEmpty()) { ++ // CraftBukkit start ++ } else { ++ org.bukkit.inventory.ItemStack armorStandItem = CraftItemStack.asCraftMirror(itemstack1); ++ org.bukkit.inventory.ItemStack playerHeldItem = CraftItemStack.asCraftMirror(stack); ++ ++ Player player1 = (Player) player.getBukkitEntity(); ++ org.bukkit.entity.ArmorStand self = (org.bukkit.entity.ArmorStand) this.getBukkitEntity(); ++ ++ EquipmentSlot slot1 = CraftEquipmentSlot.getSlot(slot); ++ EquipmentSlot hand1 = CraftEquipmentSlot.getHand(hand); ++ PlayerArmorStandManipulateEvent armorStandManipulateEvent = new PlayerArmorStandManipulateEvent(player1, self, playerHeldItem, armorStandItem, slot1, hand1); ++ this.level().getCraftServer().getPluginManager().callEvent(armorStandManipulateEvent); ++ ++ if (armorStandManipulateEvent.isCancelled()) { ++ return true; ++ } ++ ++ if (player.hasInfiniteMaterials() && itemstack1.isEmpty() && !stack.isEmpty()) { ++ // CraftBukkit end + this.setItemSlot(slot, stack.copyWithCount(1)); + return true; + } else if (!stack.isEmpty() && stack.getCount() > 1) { +@@ -427,6 +482,7 @@ + player.setItemInHand(hand, itemstack1); + return true; + } ++ } // CraftBukkit + } + + @Override +@@ -436,12 +492,24 @@ + } else if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && source.getEntity() instanceof Mob) { + return false; + } else if (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { +- this.kill(world); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } ++ this.kill(world, source); // CraftBukkit ++ // CraftBukkit end + return false; +- } else if (!this.isInvulnerableTo(world, source) && !this.invisible && !this.isMarker()) { ++ } else if (!this.isInvulnerableTo(world, source) && (true || !this.invisible) && !this.isMarker()) { // CraftBukkit ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, true, this.invisible)) { ++ return false; ++ } ++ // CraftBukkit end + if (source.is(DamageTypeTags.IS_EXPLOSION)) { +- this.brokenByAnything(world, source); +- this.kill(world); ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, source); ++ if (!event.isCancelled()) this.kill(source, false); // CraftBukkit ++ // Paper end + return false; + } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) { + if (this.isOnFire()) { +@@ -463,8 +531,8 @@ + } else { + Entity entity = source.getEntity(); + +- if (entity instanceof Player) { +- Player entityhuman = (Player) entity; ++ if (entity instanceof net.minecraft.world.entity.player.Player) { ++ net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity; + + if (!entityhuman.getAbilities().mayBuild) { + return false; +@@ -474,7 +542,7 @@ + if (source.isCreativePlayer()) { + this.playBrokenSound(); + this.showBreakingParticles(); +- this.kill(world); ++ this.kill(world, source); // CraftBukkit + return true; + } else { + long i = world.getGameTime(); +@@ -484,9 +552,9 @@ + this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); + this.lastHit = i; + } else { +- this.brokenByPlayer(world, source); ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(world, source); // Paper + this.showBreakingParticles(); +- this.kill(world); ++ if (!event.isCancelled()) this.kill(source, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...) + } + + return true; +@@ -536,7 +604,10 @@ + f1 -= amount; + if (f1 <= 0.5F) { + this.brokenByAnything(world, damageSource); +- this.kill(world); ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, damageSource); ++ if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit ++ // Paper end + } else { + this.setHealth(f1); + this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity()); +@@ -544,17 +615,17 @@ + + } + +- private void brokenByPlayer(ServerLevel world, DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper + ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + + itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); +- Block.popResource(this.level(), this.blockPosition(), itemstack); +- this.brokenByAnything(world, damageSource); ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior ++ return this.brokenByAnything(world, damageSource); // Paper + } + +- private void brokenByAnything(ServerLevel world, DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel world, DamageSource damageSource) { // Paper + this.playBrokenSound(); +- this.dropAllDeathLoot(world, damageSource); ++ // this.dropAllDeathLoot(worldserver, damagesource); // CraftBukkit - moved down + + ItemStack itemstack; + int i; +@@ -562,7 +633,7 @@ + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- Block.popResource(this.level(), this.blockPosition().above(), itemstack); ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -570,15 +641,16 @@ + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- Block.popResource(this.level(), this.blockPosition().above(), itemstack); ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.armorItems.set(i, ItemStack.EMPTY); + } + } ++ return this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above // Paper + + } + + private void playBrokenSound() { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.ARMOR_STAND_BREAK, this.getSoundSource(), 1.0F, 1.0F); ++ this.level().playSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.ARMOR_STAND_BREAK, this.getSoundSource(), 1.0F, 1.0F); + } + + @Override +@@ -609,7 +681,29 @@ + + @Override + public void tick() { ++ // Paper start - Allow ArmorStands not to tick ++ if (!this.canTick) { ++ if (this.noTickPoseDirty) { ++ this.noTickPoseDirty = false; ++ this.updatePose(); ++ } ++ ++ if (this.noTickEquipmentDirty) { ++ this.noTickEquipmentDirty = false; ++ this.detectEquipmentUpdatesPublic(); ++ } ++ ++ return; ++ } ++ // Paper end - Allow ArmorStands not to tick ++ + super.tick(); ++ // Paper start - Allow ArmorStands not to tick ++ updatePose(); ++ } ++ ++ public void updatePose() { ++ // Paper end - Allow ArmorStands not to tick + Rotations vector3f = (Rotations) this.entityData.get(ArmorStand.DATA_HEAD_POSE); + + if (!this.headPose.equals(vector3f)) { +@@ -664,9 +758,31 @@ + return this.isSmall(); + } + ++ // CraftBukkit start + @Override ++ public boolean shouldDropExperience() { ++ return true; // MC-157395, SPIGOT-5193 even baby (small) armor stands should drop ++ } ++ // CraftBukkit end ++ ++ @Override + public void kill(ServerLevel world) { +- this.remove(Entity.RemovalReason.KILLED); ++ // CraftBukkit start - pass DamageSource for kill ++ this.kill(world, null); ++ } ++ ++ public void kill(ServerLevel worldserver, DamageSource damageSource) { ++ // Paper start - make cancellable ++ this.kill(damageSource, true); ++ } ++ public void kill(DamageSource damageSource, boolean callEvent) { ++ if (callEvent) { ++ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event ++ if (event.isCancelled()) return; ++ } ++ // Paper end ++ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause ++ // CraftBukkit end + this.gameEvent(GameEvent.ENTITY_DIE); + } + +@@ -730,31 +846,37 @@ + public void setHeadPose(Rotations angle) { + this.headPose = angle; + this.entityData.set(ArmorStand.DATA_HEAD_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setBodyPose(Rotations angle) { + this.bodyPose = angle; + this.entityData.set(ArmorStand.DATA_BODY_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftArmPose(Rotations angle) { + this.leftArmPose = angle; + this.entityData.set(ArmorStand.DATA_LEFT_ARM_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightArmPose(Rotations angle) { + this.rightArmPose = angle; + this.entityData.set(ArmorStand.DATA_RIGHT_ARM_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftLegPose(Rotations angle) { + this.leftLegPose = angle; + this.entityData.set(ArmorStand.DATA_LEFT_LEG_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightLegPose(Rotations angle) { + this.rightLegPose = angle; + this.entityData.set(ArmorStand.DATA_RIGHT_LEG_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public Rotations getHeadPose() { +@@ -788,7 +910,7 @@ + + @Override + public boolean skipAttackInteraction(Entity attacker) { +- return attacker instanceof Player && !this.level().mayInteract((Player) attacker, this.blockPosition()); ++ return attacker instanceof net.minecraft.world.entity.player.Player && !this.level().mayInteract((net.minecraft.world.entity.player.Player) attacker, this.blockPosition()); + } + + @Override +@@ -882,4 +1004,13 @@ + public boolean canBeSeenByAnyone() { + return !this.isInvisible() && !this.isMarker(); + } ++ ++ // Paper start ++ @Override ++ public void move(net.minecraft.world.entity.MoverType type, Vec3 movement) { ++ if (this.canMove) { ++ super.move(type, movement); ++ } ++ } ++ // Paper end + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch new file mode 100644 index 0000000000..2f102c86b9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch @@ -0,0 +1,140 @@ +--- a/net/minecraft/world/entity/decoration/BlockAttachedEntity.java ++++ b/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +@@ -2,9 +2,6 @@ + + import com.mojang.logging.LogUtils; + import javax.annotation.Nullable; +-import net.minecraft.core.BlockPos; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +@@ -15,13 +12,24 @@ + import net.minecraft.world.level.Explosion; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ServerLevel; ++// CraftBukkit start ++import net.minecraft.tags.DamageTypeTags; ++import org.bukkit.entity.Hanging; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.hanging.HangingBreakByEntityEvent; ++import org.bukkit.event.hanging.HangingBreakEvent; ++// CraftBukkit end + + public abstract class BlockAttachedEntity extends Entity { + + private static final Logger LOGGER = LogUtils.getLogger(); +- private int checkInterval; ++ private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking + protected BlockPos pos; + + protected BlockAttachedEntity(EntityType type, Level world) { +@@ -41,10 +49,28 @@ + + if (world instanceof ServerLevel worldserver) { + this.checkBelowWorld(); +- if (this.checkInterval++ == 100) { ++ if (this.checkInterval++ == this.level().spigotConfig.hangingTickFrequency) { // Spigot + this.checkInterval = 0; + if (!this.isRemoved() && !this.survives()) { +- this.discard(); ++ // CraftBukkit start - fire break events ++ BlockState material = this.level().getBlockState(this.blockPosition()); ++ HangingBreakEvent.RemoveCause cause; ++ ++ if (!material.isAir()) { ++ // 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.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (this.isRemoved() || event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + this.dropItem(worldserver, (Entity) null); + } + } +@@ -81,6 +107,22 @@ + return false; + } else { + if (!this.isRemoved()) { ++ // CraftBukkit start - fire break events ++ Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API ++ HangingBreakEvent event; ++ if (damager != null) { ++ event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY); ++ } else { ++ event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.DEFAULT); ++ } ++ ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (this.isRemoved() || event.isCancelled()) { ++ return true; ++ } ++ // CraftBukkit end ++ + this.kill(world); + this.markHurt(); + this.dropItem(world, source.getEntity()); +@@ -101,6 +143,16 @@ + + if (world instanceof ServerLevel worldserver) { + if (!this.isRemoved() && movement.lengthSqr() > 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.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (this.isRemoved() || event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ + this.kill(worldserver); + this.dropItem(worldserver, (Entity) null); + } +@@ -109,11 +161,11 @@ + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - override correct overload + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +- if (!this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { ++ if (false && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { // CraftBukkit - not needed + this.kill(worldserver); + this.dropItem(worldserver, (Entity) null); + } +@@ -121,7 +173,16 @@ + + } + ++ // CraftBukkit start - selectively save tile position + @Override ++ public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) { ++ if (includeAll) { ++ this.addAdditionalSaveData(nbttagcompound); ++ } ++ } ++ // CraftBukkit end ++ ++ @Override + public void addAdditionalSaveData(CompoundTag nbt) { + BlockPos blockposition = this.getPos(); + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch new file mode 100644 index 0000000000..f6d7c47bb4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch @@ -0,0 +1,150 @@ +--- a/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.entity.decoration; + + import javax.annotation.Nullable; ++import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.component.DataComponents; +@@ -50,6 +51,7 @@ + private static final float HEIGHT = 0.75F; + public float dropChance; + public boolean fixed; ++ public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames + + public ItemFrame(EntityType type, Level world) { + super(type, world); +@@ -91,9 +93,15 @@ + + @Override + protected AABB calculateBoundingBox(BlockPos pos, Direction side) { ++ // CraftBukkit start - break out BB calc into own method ++ return ItemFrame.calculateBoundingBoxStatic(pos, side); ++ } ++ ++ public static AABB calculateBoundingBoxStatic(BlockPos blockposition, Direction enumdirection) { ++ // CraftBukkit end + float f = 0.46875F; +- Vec3 vec3d = Vec3.atCenterOf(pos).relative(side, -0.46875D); +- Direction.Axis enumdirection_enumaxis = side.getAxis(); ++ Vec3 vec3d = Vec3.atCenterOf(blockposition).relative(enumdirection, -0.46875D); ++ Direction.Axis enumdirection_enumaxis = enumdirection.getAxis(); + double d0 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : 0.75D; + double d1 = enumdirection_enumaxis == Direction.Axis.Y ? 0.0625D : 0.75D; + double d2 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : 0.75D; +@@ -123,9 +131,9 @@ + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param + if (!this.fixed) { +- super.push(deltaX, deltaY, deltaZ); ++ super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param + } + + } +@@ -155,6 +163,18 @@ + if (this.isInvulnerableToBase(source)) { + return false; + } else if (this.shouldDamageDropItem(source)) { ++ // CraftBukkit start - fire EntityDamageEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false) || this.isRemoved()) { ++ return true; ++ } ++ // CraftBukkit end ++ // Paper start - Add PlayerItemFrameChangeEvent ++ if (source.getEntity() instanceof Player player) { ++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE); ++ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false); ++ } ++ // Paper end - Add PlayerItemFrameChangeEvent + this.dropItem(world, source.getEntity(), false); + this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity()); + this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F); +@@ -251,7 +271,15 @@ + + public ItemStack getItem() { + return (ItemStack) this.getEntityData().get(ItemFrame.DATA_ITEM); ++ } ++ ++ // Paper start - Fix MC-123848 (spawn item frame drops above block) ++ @Nullable ++ @Override ++ public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ServerLevel serverLevel, ItemStack stack) { ++ return this.spawnAtLocation(serverLevel, stack, this.getDirection() == Direction.DOWN ? -0.6F : 0.0F); + } ++ // Paper end + + @Nullable + public MapId getFramedMapId(ItemStack stack) { +@@ -267,17 +295,23 @@ + } + + public void setItem(ItemStack value, boolean update) { +- if (!value.isEmpty()) { +- value = value.copyWithCount(1); ++ // CraftBukkit start ++ this.setItem(value, update, true); ++ } ++ ++ public void setItem(ItemStack itemstack, boolean flag, boolean playSound) { ++ // CraftBukkit end ++ if (!itemstack.isEmpty()) { ++ itemstack = itemstack.copyWithCount(1); + } + +- this.onItemChanged(value); +- this.getEntityData().set(ItemFrame.DATA_ITEM, value); +- if (!value.isEmpty()) { ++ this.onItemChanged(itemstack); ++ this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack); ++ if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set + this.playSound(this.getAddItemSound(), 1.0F, 1.0F); + } + +- if (update && this.pos != null) { ++ if (flag && this.pos != null) { + this.level().updateNeighbourForOutputSignal(this.pos, Blocks.AIR); + } + +@@ -301,6 +335,7 @@ + } + + private void onItemChanged(ItemStack stack) { ++ this.cachedMapId = stack.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames + if (!stack.isEmpty() && stack.getFrame() != this) { + stack.setEntityRepresentation(this); + } +@@ -386,7 +421,13 @@ + if (worldmap != null && worldmap.isTrackedCountOverLimit(256)) { + return InteractionResult.FAIL; + } else { +- this.setItem(itemstack); ++ // Paper start - Add PlayerItemFrameChangeEvent ++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack())); ++ // Paper end - Add PlayerItemFrameChangeEvent + this.gameEvent(GameEvent.BLOCK_CHANGE, player); + itemstack.consume(1, player); + return InteractionResult.SUCCESS; +@@ -395,6 +436,13 @@ + return InteractionResult.PASS; + } + } else { ++ // Paper start - Add PlayerItemFrameChangeEvent ++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false); ++ // Paper end - Add PlayerItemFrameChangeEvent + this.playSound(this.getRotateItemSound(), 1.0F, 1.0F); + this.setRotation(this.getRotation() + 1); + this.gameEvent(GameEvent.BLOCK_CHANGE, player); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch new file mode 100644 index 0000000000..c569c75fde --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch @@ -0,0 +1,87 @@ +--- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -8,9 +8,11 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.server.level.ServerEntity; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.tags.BlockTags; + import net.minecraft.world.InteractionHand; +@@ -26,6 +28,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class LeashFenceKnotEntity extends BlockAttachedEntity { + +@@ -85,6 +90,15 @@ + Leashable leashable = (Leashable) iterator.next(); + + if (leashable.getLeashHolder() == player) { ++ // CraftBukkit start ++ if (leashable instanceof Entity leashed) { ++ if (CraftEventFactory.callPlayerLeashEntityEvent(leashed, this, player, hand).isCancelled()) { ++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(leashed, leashable.getLeashHolder())); ++ flag = true; // Also set true when the event is cancelled otherwise it tries to unleash the entities ++ continue; ++ } ++ } ++ // CraftBukkit end + leashable.setLeashedTo(this, true); + flag = true; + } +@@ -93,18 +107,43 @@ + boolean flag1 = false; + + if (!flag) { +- this.discard(); +- if (player.getAbilities().instabuild) { ++ // CraftBukkit start - Move below ++ // this.discard(); ++ boolean die = true; ++ // CraftBukkit end ++ if (true || player.getAbilities().instabuild) { // CraftBukkit - Process for non-creative as well + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { + Leashable leashable1 = (Leashable) iterator1.next(); + + if (leashable1.isLeashed() && leashable1.getLeashHolder() == this) { +- leashable1.removeLeash(); ++ // CraftBukkit start ++ boolean dropLeash = !player.hasInfiniteMaterials(); ++ if (leashable1 instanceof Entity leashed) { ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash); ++ dropLeash = event.isDropLeash(); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent ++ die = false; ++ continue; ++ } ++ } ++ if (!dropLeash) { // Paper - Expand EntityUnleashEvent ++ leashable1.removeLeash(); ++ } else { ++ leashable1.dropLeash(); ++ } ++ // CraftBukkit end + flag1 = true; + } + } ++ // CraftBukkit start ++ if (die) { ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause ++ } ++ // CraftBukkit end + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch new file mode 100644 index 0000000000..122466b765 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch @@ -0,0 +1,54 @@ +--- a/net/minecraft/world/entity/decoration/Painting.java ++++ b/net/minecraft/world/entity/decoration/Painting.java +@@ -72,7 +72,7 @@ + public static Optional create(Level world, BlockPos pos, Direction facing) { + Painting entitypainting = new Painting(world, pos); + List> list = new ArrayList(); +- Iterable iterable = world.registryAccess().lookupOrThrow(Registries.PAINTING_VARIANT).getTagOrEmpty(PaintingVariantTags.PLACEABLE); ++ Iterable> iterable = world.registryAccess().lookupOrThrow(Registries.PAINTING_VARIANT).getTagOrEmpty(PaintingVariantTags.PLACEABLE); // CraftBukkit - decompile error + + Objects.requireNonNull(list); + iterable.forEach(list::add); +@@ -138,22 +138,32 @@ + + @Override + protected AABB calculateBoundingBox(BlockPos pos, Direction side) { +- float f = 0.46875F; +- Vec3 vec3d = Vec3.atCenterOf(pos).relative(side, -0.46875D); ++ // CraftBukkit start + PaintingVariant paintingvariant = (PaintingVariant) this.getVariant().value(); +- double d0 = this.offsetForPaintingSize(paintingvariant.width()); +- double d1 = this.offsetForPaintingSize(paintingvariant.height()); +- Direction enumdirection1 = side.getCounterClockWise(); ++ return Painting.calculateBoundingBoxStatic(pos, side, paintingvariant.width(), paintingvariant.height()); ++ } ++ ++ public static AABB calculateBoundingBoxStatic(BlockPos blockposition, Direction enumdirection, int width, int height) { ++ // CraftBukkit end ++ float f = 0.46875F; ++ Vec3 vec3d = Vec3.atCenterOf(blockposition).relative(enumdirection, -0.46875D); ++ // CraftBukkit start ++ double d0 = Painting.offsetForPaintingSize(width); ++ double d1 = Painting.offsetForPaintingSize(height); ++ // CraftBukkit end ++ Direction enumdirection1 = enumdirection.getCounterClockWise(); + Vec3 vec3d1 = vec3d.relative(enumdirection1, d0).relative(Direction.UP, d1); +- Direction.Axis enumdirection_enumaxis = side.getAxis(); +- double d2 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : (double) paintingvariant.width(); +- double d3 = (double) paintingvariant.height(); +- double d4 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : (double) paintingvariant.width(); ++ Direction.Axis enumdirection_enumaxis = enumdirection.getAxis(); ++ // CraftBukkit start ++ double d2 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : (double) width; ++ double d3 = (double) height; ++ double d4 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : (double) width; ++ // CraftBukkit end + + return AABB.ofSize(vec3d1, d2, d3, d4); + } + +- private double offsetForPaintingSize(int length) { ++ private static double offsetForPaintingSize(int length) { // CraftBukkit - static + return length % 2 == 0 ? 0.5D : 0.0D; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch new file mode 100644 index 0000000000..8f36430b05 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch @@ -0,0 +1,162 @@ +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -52,6 +52,11 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end ++ + public class FallingBlockEntity extends Entity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -66,6 +71,7 @@ + public CompoundTag blockData; + public boolean forceTickAfterTeleportToDuplicate; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); ++ public boolean autoExpire = true; // Paper - Expand FallingBlock API + + public FallingBlockEntity(EntityType type, Level world) { + super(type, world); +@@ -87,10 +93,17 @@ + } + + public static FallingBlockEntity fall(Level world, BlockPos pos, BlockState state) { +- FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, state.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) state.setValue(BlockStateProperties.WATERLOGGED, false) : state); ++ // CraftBukkit start ++ return FallingBlockEntity.fall(world, pos, state, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } + +- world.setBlock(pos, state.getFluidState().createLegacyBlock(), 3); +- world.addFreshEntity(entityfallingblock); ++ public static FallingBlockEntity fall(Level world, BlockPos blockposition, BlockState iblockdata, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // CraftBukkit end ++ FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, iblockdata.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) iblockdata.setValue(BlockStateProperties.WATERLOGGED, false) : iblockdata); ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entityfallingblock, blockposition, iblockdata.getFluidState().createLegacyBlock())) return entityfallingblock; // CraftBukkit ++ ++ world.setBlock(blockposition, iblockdata.getFluidState().createLegacyBlock(), 3); ++ world.addFreshEntity(entityfallingblock, spawnReason); // CraftBukkit + return entityfallingblock; + } + +@@ -139,7 +152,7 @@ + @Override + public void tick() { + if (this.blockState.isAir()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + Block block = this.blockState.getBlock(); + +@@ -147,6 +160,16 @@ + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); ++ // Paper start - Configurable falling blocks height nerf ++ if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { ++ if (this.dropItem && this.level() instanceof final ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ++ this.spawnAtLocation(serverLevel, block); ++ } ++ ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); ++ return; ++ } ++ // Paper end - Configurable falling blocks height nerf + this.handlePortal(); + Level world = this.level(); + +@@ -169,12 +192,12 @@ + } + + if (!this.onGround() && !flag1) { +- if (this.time > 100 && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || this.time > 600) { ++ if ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API + if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.spawnAtLocation(worldserver, (ItemLike) block); + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + } + } else { + BlockState iblockdata = this.level().getBlockState(blockposition); +@@ -191,9 +214,15 @@ + this.blockState = (BlockState) this.blockState.setValue(BlockStateProperties.WATERLOGGED, true); + } + ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, this.blockState)) { ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // SPIGOT-6586 called before the event in previous versions ++ return; ++ } ++ // CraftBukkit end + if (this.level().setBlock(blockposition, this.blockState, 3)) { + ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition))); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + if (block instanceof Fallable) { + ((Fallable) block).onLand(this.level(), blockposition, this.blockState, iblockdata, this); + } +@@ -221,19 +250,19 @@ + } + } + } else if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + this.callOnBrokenAfterFall(block, blockposition); + this.spawnAtLocation(worldserver, (ItemLike) block); + } + } else { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.callOnBrokenAfterFall(block, blockposition); + this.spawnAtLocation(worldserver, (ItemLike) block); + } + } + } else { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + this.callOnBrokenAfterFall(block, blockposition); + } + } +@@ -310,6 +339,7 @@ + } + + nbt.putBoolean("CancelDrop", this.cancelDrop); ++ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API + } + + @Override +@@ -328,7 +358,7 @@ + this.dropItem = nbt.getBoolean("DropItem"); + } + +- if (nbt.contains("TileEntityData", 10)) { ++ if (nbt.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks + this.blockData = nbt.getCompound("TileEntityData").copy(); + } + +@@ -337,6 +367,11 @@ + this.blockState = Blocks.SAND.defaultBlockState(); + } + ++ // Paper start - Expand FallingBlock API ++ if (nbt.contains("Paper.AutoExpire")) { ++ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); ++ } ++ // Paper end - Expand FallingBlock API + } + + public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) { +@@ -395,7 +430,7 @@ + boolean flag = (resourcekey1 == Level.END || resourcekey == Level.END) && resourcekey1 != resourcekey; + Entity entity = super.teleport(teleportTarget); + +- this.forceTickAfterTeleportToDuplicate = entity != null && flag; ++ this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper + return entity; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch new file mode 100644 index 0000000000..0bc136847c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -0,0 +1,389 @@ +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -5,18 +5,6 @@ + import java.util.Objects; + import java.util.UUID; + import javax.annotation.Nullable; +-import net.minecraft.core.BlockPos; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.network.chat.Component; +-import net.minecraft.network.syncher.EntityDataAccessor; +-import net.minecraft.network.syncher.EntityDataSerializers; +-import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.sounds.SoundSource; +-import net.minecraft.stats.Stats; +-import net.minecraft.tags.FluidTags; +-import net.minecraft.tags.ItemTags; +-import net.minecraft.util.Mth; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +@@ -24,7 +12,6 @@ + import net.minecraft.world.entity.MoverType; + import net.minecraft.world.entity.SlotAccess; + import net.minecraft.world.entity.TraceableEntity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Explosion; +@@ -33,6 +20,27 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.phys.Vec3; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.network.chat.Component; ++import net.minecraft.network.syncher.EntityDataAccessor; ++import net.minecraft.network.syncher.EntityDataSerializers; ++import net.minecraft.network.syncher.SynchedEntityData; ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.sounds.SoundSource; ++import net.minecraft.stats.Stats; ++import net.minecraft.tags.FluidTags; ++import net.minecraft.tags.ItemTags; ++import net.minecraft.util.Mth; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.EntityPickupItemEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerPickupItemEvent; ++// CraftBukkit end ++import org.bukkit.event.player.PlayerAttemptPickupItemEvent; // Paper + + public class ItemEntity extends Entity implements TraceableEntity { + +@@ -52,6 +60,10 @@ + @Nullable + public UUID target; + public final float bobOffs; ++ // private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit // Paper - remove anti tick skipping measures / wall time ++ public boolean canMobPickup = true; // Paper - Item#canEntityPickup ++ private int despawnRate = -1; // Paper - Alternative item-despawn-rate ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + public ItemEntity(EntityType type, Level world) { + super(type, world); +@@ -61,7 +73,12 @@ + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { +- this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D); ++ // Paper start - Don't use level random in entity constructors (to make them thread-safe) ++ this(EntityType.ITEM, world); ++ this.setPos(x, y, z); ++ this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D); ++ this.setItem(stack); ++ // Paper end - Don't use level random in entity constructors + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { +@@ -133,12 +150,14 @@ + @Override + public void tick() { + if (this.getItem().isEmpty()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + super.tick(); ++ // Paper start - remove anti tick skipping measures / wall time - revert to vanilla + if (this.pickupDelay > 0 && this.pickupDelay != 32767) { + --this.pickupDelay; + } ++ // Paper end - remove anti tick skipping measures / wall time - revert to vanilla + + this.xo = this.getX(); + this.yo = this.getY(); +@@ -162,12 +181,16 @@ + } + } + +- if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { ++ if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change; ActivationRange immunity + this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); + float f = 0.98F; + +- if (this.onGround()) { ++ // Paper start - Friction API ++ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { ++ f = 1F; ++ } else if (this.onGround()) { ++ // Paper end - Friction API + f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; + } + +@@ -188,9 +211,11 @@ + this.mergeWithNeighbours(); + } + ++ // Paper - remove anti tick skipping measures / wall time - revert to vanilla /* CraftBukkit start - moved up + if (this.age != -32768) { + ++this.age; + } ++ // CraftBukkit end */ + + this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing(); + if (!this.level().isClientSide) { +@@ -201,14 +226,44 @@ + } + } + +- if (!this.level().isClientSide && this.age >= 6000) { +- this.discard(); ++ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate ++ // CraftBukkit start - fire ItemDespawnEvent ++ if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { ++ this.age = 0; ++ return; ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } + } + ++ // Spigot start - copied from above + @Override ++ public void inactiveTick() { ++ // Paper start - remove anti tick skipping measures / wall time - copied from above ++ if (this.pickupDelay > 0 && this.pickupDelay != 32767) { ++ --this.pickupDelay; ++ } ++ if (this.age != -32768) { ++ ++this.age; ++ } ++ // Paper end - remove anti tick skipping measures / wall time - copied from above ++ ++ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate ++ // CraftBukkit start - fire ItemDespawnEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { ++ this.age = 0; ++ return; ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ } ++ } ++ // Spigot end ++ ++ @Override + public BlockPos getBlockPosBelowThatAffectsMyMovement() { + return this.getOnPos(0.999999F); + } +@@ -229,7 +284,10 @@ + + private void mergeWithNeighbours() { + if (this.isMergable()) { +- List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.5D, 0.0D, 0.5D), (entityitem) -> { ++ // Spigot start ++ double radius = this.level().spigotConfig.itemMerge; ++ List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(radius, this.level().paperConfig().entities.behavior.onlyMergeItemsHorizontally ? 0 : radius - 0.5D, radius), (entityitem) -> { // Paper - configuration to only merge items horizontally ++ // Spigot end + return entityitem != this && entityitem.isMergable(); + }); + Iterator iterator = list.iterator(); +@@ -238,6 +296,14 @@ + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (entityitem.isMergable()) { ++ // Paper start - Fix items merging through walls ++ if (this.level().paperConfig().fixes.fixItemsMergingThroughWalls) { ++ if (this.level().clipDirect(this.position(), entityitem.position(), ++ net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.HitResult.Type.BLOCK) { ++ continue; ++ } ++ } ++ // Paper end - Fix items merging through walls + this.tryToMerge(entityitem); + if (this.isRemoved()) { + break; +@@ -251,7 +317,7 @@ + private boolean isMergable() { + ItemStack itemstack = this.getItem(); + +- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize(); ++ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate + } + + private void tryToMerge(ItemEntity other) { +@@ -259,7 +325,7 @@ + ItemStack itemstack1 = other.getItem(); + + if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) { +- if (itemstack1.getCount() < itemstack.getCount()) { ++ if (true || itemstack1.getCount() < itemstack.getCount()) { // Spigot + ItemEntity.merge(this, itemstack, other, itemstack1); + } else { + ItemEntity.merge(other, itemstack1, this, itemstack); +@@ -287,11 +353,16 @@ + } + + private static void merge(ItemEntity targetEntity, ItemStack targetStack, ItemEntity sourceEntity, ItemStack sourceStack) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callItemMergeEvent(sourceEntity, targetEntity)) { ++ return; ++ } ++ // CraftBukkit end + ItemEntity.merge(targetEntity, targetStack, sourceStack); + targetEntity.pickupDelay = Math.max(targetEntity.pickupDelay, sourceEntity.pickupDelay); + targetEntity.age = Math.min(targetEntity.age, sourceEntity.age); + if (sourceStack.isEmpty()) { +- sourceEntity.discard(); ++ sourceEntity.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause); + } + + } +@@ -320,12 +391,17 @@ + } else if (!this.getItem().canBeHurtBy(source)) { + return false; + } else { ++ // CraftBukkit start ++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) { ++ return false; ++ } ++ // CraftBukkit end + this.markHurt(); + this.health = (int) ((float) this.health - amount); + this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); + if (this.health <= 0) { + this.getItem().onDestroyed(this); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } + + return true; +@@ -339,6 +415,11 @@ + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); + nbt.putShort("PickupDelay", (short) this.pickupDelay); +@@ -381,23 +462,98 @@ + this.setItem(ItemStack.EMPTY); + } + ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API ++ + if (this.getItem().isEmpty()) { +- this.discard(); ++ this.discard(null); // CraftBukkit - add Bukkit remove cause + } + + } + + @Override +- public void playerTouch(Player player) { ++ public void playerTouch(net.minecraft.world.entity.player.Player player) { + if (!this.level().isClientSide) { + ItemStack itemstack = this.getItem(); + Item item = itemstack.getItem(); + int i = itemstack.getCount(); ++ ++ // CraftBukkit start - fire PlayerPickupItemEvent ++ int canHold = player.getInventory().canHold(itemstack); ++ int remaining = i - canHold; ++ boolean flyAtPlayer = false; // Paper ++ ++ // Paper start - PlayerAttemptPickupItemEvent ++ if (this.pickupDelay <= 0) { ++ PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ this.level().getCraftServer().getPluginManager().callEvent(attemptEvent); ++ ++ flyAtPlayer = attemptEvent.getFlyAtPlayer(); ++ if (attemptEvent.isCancelled()) { ++ if (flyAtPlayer) { ++ player.take(this, i); ++ } ++ ++ return; ++ } ++ } ++ // Paper end - PlayerAttemptPickupItemEvent + ++ if (this.pickupDelay <= 0 && canHold > 0) { ++ itemstack.setCount(canHold); ++ // Call legacy event ++ PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ playerEvent.setCancelled(!playerEvent.getPlayer().getCanPickupItems()); ++ this.level().getCraftServer().getPluginManager().callEvent(playerEvent); ++ flyAtPlayer = playerEvent.getFlyAtPlayer(); // Paper ++ if (playerEvent.isCancelled()) { ++ itemstack.setCount(i); // SPIGOT-5294 - restore count ++ // Paper start ++ if (flyAtPlayer) { ++ player.take(this, i); ++ } ++ // Paper end ++ return; ++ } ++ ++ // Call newer event afterwards ++ EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); ++ entityEvent.setCancelled(!entityEvent.getEntity().getCanPickupItems()); ++ this.level().getCraftServer().getPluginManager().callEvent(entityEvent); ++ if (entityEvent.isCancelled()) { ++ itemstack.setCount(i); // SPIGOT-5294 - restore count ++ return; ++ } ++ ++ // Update the ItemStack if it was changed in the event ++ ItemStack current = this.getItem(); ++ if (!itemstack.equals(current)) { ++ itemstack = current; ++ } else { ++ itemstack.setCount(canHold + remaining); // = i ++ } ++ ++ // Possibly < 0; fix here so we do not have to modify code below ++ this.pickupDelay = 0; ++ } else if (this.pickupDelay == 0) { ++ // ensure that the code below isn't triggered if canHold says we can't pick the items up ++ this.pickupDelay = -1; ++ } ++ // CraftBukkit end ++ + if (this.pickupDelay == 0 && (this.target == null || this.target.equals(player.getUUID())) && player.getInventory().add(itemstack)) { ++ if (flyAtPlayer) // Paper - PlayerPickupItemEvent + player.take(this, i); + if (itemstack.isEmpty()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + itemstack.setCount(i); + } + +@@ -438,6 +594,7 @@ + + public void setItem(ItemStack stack) { + this.getEntityData().set(ItemEntity.DATA_ITEM, stack); ++ this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate + } + + @Override +@@ -492,7 +649,7 @@ + + public void makeFakeItem() { + this.setNeverPickUp(); +- this.age = 5999; ++ this.age = this.despawnRate - 1; // Spigot // Paper - Alternative item-despawn-rate + } + + public static float getSpin(float f, float f1) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch new file mode 100644 index 0000000000..1120fe2bdf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch @@ -0,0 +1,116 @@ +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -27,6 +27,12 @@ + import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.portal.TeleportTransition; + ++// CraftBukkit start; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end ++ + public class PrimedTnt extends Entity implements TraceableEntity { + + private static final EntityDataAccessor DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT); +@@ -51,6 +57,7 @@ + public LivingEntity owner; + private boolean usedPortal; + public float explosionPower; ++ public boolean isIncendiary = false; // CraftBukkit - add field + + public PrimedTnt(EntityType type, Level world) { + super(type, world); +@@ -61,7 +68,7 @@ + public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { + this(EntityType.TNT, world); + this.setPos(x, y, z); +- double d3 = world.random.nextDouble() * 6.2831854820251465D; ++ double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - Don't use level random in entity constructors + + this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D); + this.setFuse(80); +@@ -94,10 +101,17 @@ + + @Override + public void tick() { ++ if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot + this.handlePortal(); + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); ++ // Paper start - Configurable TNT height nerf ++ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); ++ return; ++ } ++ // Paper end - Configurable TNT height nerf + this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); + if (this.onGround()) { + this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); +@@ -107,10 +121,13 @@ + + this.setFuse(i); + if (i <= 0) { +- this.discard(); ++ // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event ++ // this.discard(); + if (!this.level().isClientSide) { + this.explode(); + } ++ this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause ++ // CraftBukkit end + } else { + this.updateInWaterStateAndDoFluidPushing(); + if (this.level().isClientSide) { +@@ -118,10 +135,37 @@ + } + } + ++ // Paper start - Option to prevent TNT from moving in water ++ if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) { ++ /* ++ * Author: Jedediah Smith ++ */ ++ // Send position and velocity updates to nearby players on every tick while the TNT is in water. ++ // This does pretty well at keeping their clients in sync with the server. ++ net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId()); ++ if (ete != null) { ++ net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); ++ net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); ++ ++ ete.seenBy.stream() ++ .filter(viewer -> (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) ++ .forEach(viewer -> { ++ viewer.send(velocityPacket); ++ viewer.send(positionPacket); ++ }); ++ } ++ } ++ // Paper end - Option to prevent TNT from moving in water + } + + private void explode() { +- this.level().explode(this, Explosion.getDefaultDamageSource(this.level(), this), this.usedPortal ? PrimedTnt.USED_PORTAL_DAMAGE_CALCULATOR : null, this.getX(), this.getY(0.0625D), this.getZ(), this.explosionPower, false, Level.ExplosionInteraction.TNT); ++ // CraftBukkit start ++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); ++ if (event.isCancelled()) { ++ return; ++ } ++ this.level().explode(this, Explosion.getDefaultDamageSource(this.level(), this), this.usedPortal ? PrimedTnt.USED_PORTAL_DAMAGE_CALCULATOR : null, this.getX(), this.getY(0.0625D), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.TNT); ++ // CraftBukkit end + } + + @Override +@@ -198,4 +242,11 @@ + public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) { + return false; + } ++ ++ // Paper start - Option to prevent TNT from moving in water ++ @Override ++ public boolean isPushedByFluid() { ++ return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); ++ } ++ // Paper end - Option to prevent TNT from moving in water + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch new file mode 100644 index 0000000000..d726409faf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -97,9 +97,15 @@ + + abstract SoundEvent getStepSound(); + ++ // Paper start - shouldBurnInDay API ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Paper end - shouldBurnInDay API ++ + @Override + public void aiStep() { +- boolean flag = this.isSunBurnTick(); ++ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API + + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); +@@ -152,7 +158,7 @@ + this.populateDefaultEquipmentSlots(randomsource, difficulty); + this.populateDefaultEquipmentEnchantments(world, randomsource, difficulty); + this.reassessWeaponGoal(); +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); ++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot + if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { + LocalDate localdate = LocalDate.now(); + int i = localdate.get(ChronoField.DAY_OF_MONTH); +@@ -209,7 +215,17 @@ + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +- Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); ++ // CraftBukkit start ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper - improve entity shhot bow event - add arrow stack to event ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { ++ Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); ++ } ++ // CraftBukkit end + } + + this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +@@ -233,11 +249,24 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.reassessWeaponGoal(); ++ // Paper start - shouldBurnInDay API ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end - shouldBurnInDay API + } + ++ // Paper start - shouldBurnInDay API + @Override +- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +- super.setItemSlot(slot, stack); ++ public void addAdditionalSaveData(CompoundTag nbt) { ++ super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); ++ } ++ // Paper end - shouldBurnInDay API ++ ++ @Override ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change ++ super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change + if (!this.level().isClientSide) { + this.reassessWeaponGoal(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch new file mode 100644 index 0000000000..1cd68614bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch @@ -0,0 +1,72 @@ +--- a/net/minecraft/world/entity/monster/Bogged.java ++++ b/net/minecraft/world/entity/monster/Bogged.java +@@ -27,6 +27,7 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class Bogged extends AbstractSkeleton implements Shearable { + +@@ -79,7 +80,20 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ // CraftBukkit start ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ // this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients // Paper - no longer needed ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops ++ } ++ // CraftBukkit end ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -133,15 +147,36 @@ + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.BOGGED_SHEAR, shears, (ignored, stack) -> { ++ drops.add(stack); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.BOGGED_SHEAR, shearedSoundCategory, 1.0F, 1.0F); +- this.spawnShearedMushrooms(world, shears); ++ this.spawnShearedMushrooms(world, shears, drops); // Paper - custom shear drops + this.setSheared(true); + } + +- private void spawnShearedMushrooms(ServerLevel world, ItemStack shears) { +- this.dropFromShearingLootTable(world, BuiltInLootTables.BOGGED_SHEAR, shears, (worldserver1, itemstack1) -> { ++ // Paper start - custom shear drops ++ private void spawnShearedMushrooms(ServerLevel world, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles ++ drops.forEach(itemstack1 -> { ++ // Paper end - custom shear drops + this.spawnAtLocation(worldserver1, itemstack1, this.getBbHeight()); + }); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch new file mode 100644 index 0000000000..c2449e2257 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/CaveSpider.java ++++ b/net/minecraft/world/entity/monster/CaveSpider.java +@@ -40,7 +40,7 @@ + } + + if (b0 > 0) { +- ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this); ++ ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch new file mode 100644 index 0000000000..ea4e74957f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch @@ -0,0 +1,132 @@ +--- a/net/minecraft/world/entity/monster/Creeper.java ++++ b/net/minecraft/world/entity/monster/Creeper.java +@@ -42,6 +42,13 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end ++ + public class Creeper extends Monster { + + private static final EntityDataAccessor DATA_SWELL_DIR = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.INT); +@@ -52,6 +59,7 @@ + public int maxSwell = 30; + public int explosionRadius = 3; + private int droppedSkulls; ++ public Entity entityIgniter; // CraftBukkit + + public Creeper(EntityType type, Level world) { + super(type, world); +@@ -125,7 +133,7 @@ + } + + if (nbt.getBoolean("ignited")) { +- this.ignite(); ++ this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event + } + + } +@@ -214,9 +222,20 @@ + @Override + public void thunderHit(ServerLevel world, LightningBolt lightning) { + super.thunderHit(world, lightning); ++ // CraftBukkit start ++ if (CraftEventFactory.callCreeperPowerEvent(this, lightning, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.entityData.set(Creeper.DATA_IS_POWERED, true); + } + ++ // CraftBukkit start ++ public void setPowered(boolean powered) { ++ this.entityData.set(Creeper.DATA_IS_POWERED, powered); ++ } ++ // CraftBukkit end ++ + @Override + protected InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack itemstack = player.getItemInHand(hand); +@@ -226,8 +245,9 @@ + + this.level().playSound(player, this.getX(), this.getY(), this.getZ(), soundeffect, this.getSoundSource(), 1.0F, this.random.nextFloat() * 0.4F + 0.8F); + if (!this.level().isClientSide) { ++ this.entityIgniter = player; // CraftBukkit + this.ignite(); +- if (!itemstack.isDamageableItem()) { ++ if (itemstack.getMaxDamage() == 0) { // CraftBukkit - fix MC-264285: unbreakable flint and steels are completely consumed when igniting a creeper + itemstack.shrink(1); + } else { + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); +@@ -246,11 +266,21 @@ + if (world instanceof ServerLevel worldserver) { + float f = this.isPowered() ? 2.0F : 1.0F; + ++ // CraftBukkit start ++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false); ++ if (!event.isCancelled()) { ++ // CraftBukkit end + this.dead = true; +- worldserver.explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionRadius * f, Level.ExplosionInteraction.MOB); ++ worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) + this.spawnLingeringCloud(); + this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause ++ // CraftBukkit start ++ } else { ++ this.swell = 0; ++ this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper ++ } ++ // CraftBukkit end + } + + } +@@ -258,9 +288,10 @@ + private void spawnLingeringCloud() { + Collection collection = this.getActiveEffects(); + +- if (!collection.isEmpty()) { ++ if (!collection.isEmpty() && !this.level().paperConfig().entities.behavior.disableCreeperLingeringEffect) { // Paper - Option to disable creeper lingering effect + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + ++ entityareaeffectcloud.setOwner(this); // CraftBukkit + entityareaeffectcloud.setRadius(2.5F); + entityareaeffectcloud.setRadiusOnUse(-0.5F); + entityareaeffectcloud.setWaitTime(10); +@@ -274,7 +305,7 @@ + entityareaeffectcloud.addEffect(new MobEffectInstance(mobeffect)); + } + +- this.level().addFreshEntity(entityareaeffectcloud); ++ this.level().addFreshEntity(entityareaeffectcloud, CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit + } + + } +@@ -284,9 +315,20 @@ + } + + public void ignite() { +- this.entityData.set(Creeper.DATA_IS_IGNITED, true); ++ // Paper start - CreeperIgniteEvent ++ setIgnited(true); + } + ++ public void setIgnited(boolean ignited) { ++ if (isIgnited() != ignited) { ++ com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); ++ if (event.callEvent()) { ++ this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); ++ } ++ } ++ // Paper end - CreeperIgniteEvent ++ } ++ + public boolean canDropMobsSkull() { + return this.isPowered() && this.droppedSkulls < 1; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch new file mode 100644 index 0000000000..938442254b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/Drowned.java ++++ b/net/minecraft/world/entity/monster/Drowned.java +@@ -85,7 +85,7 @@ + this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target))); +- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); ++ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch new file mode 100644 index 0000000000..b270c246e4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/ElderGuardian.java ++++ b/net/minecraft/world/entity/monster/ElderGuardian.java +@@ -67,7 +67,7 @@ + super.customServerAiStep(world); + if ((this.tickCount + this.getId()) % 1200 == 0) { + MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2); +- List list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200); ++ List list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent + + list.forEach((entityplayer) -> { + entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch new file mode 100644 index 0000000000..512e3e45cf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch @@ -0,0 +1,157 @@ +--- a/net/minecraft/world/entity/monster/EnderMan.java ++++ b/net/minecraft/world/entity/monster/EnderMan.java +@@ -68,6 +68,10 @@ + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityTargetEvent; ++// CraftBukkit end + + public class EnderMan extends Monster implements NeutralMob { + +@@ -112,10 +116,26 @@ + + @Override + public void setTarget(@Nullable LivingEntity target) { +- super.setTarget(target); ++ // CraftBukkit start - fire event ++ this.setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true); ++ } ++ ++ // Paper start - EndermanEscapeEvent ++ private boolean tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason reason) { ++ return new com.destroystokyo.paper.event.entity.EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent(); ++ } ++ // Paper end - EndermanEscapeEvent ++ ++ @Override ++ public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { ++ if (!super.setTarget(entityliving, reason, fireEvent)) { ++ return false; ++ } ++ entityliving = this.getTarget(); ++ // CraftBukkit end + AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); + +- if (target == null) { ++ if (entityliving == null) { + this.targetChangeTime = 0; + this.entityData.set(EnderMan.DATA_CREEPY, false); + this.entityData.set(EnderMan.DATA_STARED_AT, false); +@@ -127,6 +147,7 @@ + attributemodifiable.addTransientModifier(EnderMan.SPEED_MODIFIER_ATTACKING); + } + } ++ return true; + + } + +@@ -212,6 +233,14 @@ + } + + boolean isBeingStaredBy(Player player) { ++ // Paper start - EndermanAttackPlayerEvent ++ final boolean shouldAttack = isBeingStaredBy0(player); ++ final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); ++ event.setCancelled(!shouldAttack); ++ return event.callEvent(); ++ } ++ private boolean isBeingStaredBy0(Player player) { ++ // Paper end - EndermanAttackPlayerEvent + return !LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM.test(player) ? false : this.isLookingAtMe(player, 0.025D, true, false, new double[]{this.getEyeY()}); + } + +@@ -241,7 +270,7 @@ + if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) { + float f = this.getLightLevelDependentMagicValue(); + +- if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) { ++ if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent + this.setTarget((LivingEntity) null); + this.teleport(); + } +@@ -367,11 +396,13 @@ + } else { + flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount); + ++ if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent + for (int i = 0; i < 64; ++i) { + if (this.teleport()) { + return true; + } + } ++ } // Paper - EndermanEscapeEvent + + return flag1; + } +@@ -397,6 +428,16 @@ + this.entityData.set(EnderMan.DATA_STARED_AT, true); + } + ++ // Paper start ++ public void setCreepy(boolean creepy) { ++ this.entityData.set(EnderMan.DATA_CREEPY, creepy); ++ } ++ ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public boolean requiresCustomPersistence() { + return super.requiresCustomPersistence() || this.getCarriedBlock() != null; +@@ -457,7 +498,8 @@ + int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 2.0D); + int k = Mth.floor(this.enderman.getZ() - 1.0D + randomsource.nextDouble() * 2.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks ++ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata1 = world.getBlockState(blockposition1); + BlockState iblockdata2 = this.enderman.getCarriedBlock(); +@@ -465,9 +507,11 @@ + if (iblockdata2 != null) { + iblockdata2 = Block.updateFromNeighbourShapes(iblockdata2, this.enderman.level(), blockposition); + if (this.canPlaceBlock(world, blockposition, iblockdata2, iblockdata, iblockdata1, blockposition1)) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata2)) { // CraftBukkit - Place event + world.setBlock(blockposition, iblockdata2, 3); + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this.enderman, iblockdata2)); + this.enderman.setCarriedBlock((BlockState) null); ++ } // CraftBukkit + } + + } +@@ -499,16 +543,19 @@ + int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 3.0D); + int k = Mth.floor(this.enderman.getZ() - 2.0D + randomsource.nextDouble() * 4.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks ++ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks + Vec3 vec3d = new Vec3((double) this.enderman.getBlockX() + 0.5D, (double) j + 0.5D, (double) this.enderman.getBlockZ() + 0.5D); + Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D); + BlockHitResult movingobjectpositionblock = world.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman)); + boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition); + + if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state + world.removeBlock(blockposition, false); + world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata)); + this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState()); ++ } // CraftBukkit + } + + } +@@ -592,7 +639,7 @@ + } else { + if (this.target != null && !this.enderman.isPassenger()) { + if (this.enderman.isBeingStaredBy((Player) this.target)) { +- if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D) { ++ if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D && this.enderman.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.STARE)) { // Paper - EndermanEscapeEvent + this.enderman.teleport(); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch new file mode 100644 index 0000000000..9a32ade2bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/monster/Endermite.java ++++ b/net/minecraft/world/entity/monster/Endermite.java +@@ -24,6 +24,9 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Endermite extends Monster { + +@@ -113,7 +116,7 @@ + } + + if (this.life >= 2400) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch new file mode 100644 index 0000000000..d8921518a6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/Evoker.java ++++ b/net/minecraft/world/entity/monster/Evoker.java +@@ -212,7 +212,7 @@ + worldserver.getScoreboard().addPlayerToTeam(entityvex.getScoreboardName(), scoreboardteam); + } + +- worldserver.addFreshEntityWithPassengers(entityvex); ++ worldserver.addFreshEntityWithPassengers(entityvex, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPELL); // CraftBukkit - Add SpawnReason + worldserver.gameEvent((Holder) GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of((Entity) Evoker.this)); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch new file mode 100644 index 0000000000..e91f483915 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/entity/monster/Ghast.java ++++ b/net/minecraft/world/entity/monster/Ghast.java +@@ -64,7 +64,13 @@ + + public int getExplosionPower() { + return this.explosionPower; ++ } ++ ++ // Paper start ++ public void setExplosionPower(int explosionPower) { ++ this.explosionPower = explosionPower; + } ++ // Paper end + + @Override + protected boolean shouldDespawnInPeaceful() { +@@ -333,6 +339,8 @@ + + LargeFireball entitylargefireball = new LargeFireball(world, this.ghast, vec3d1.normalize(), this.ghast.getExplosionPower()); + ++ // CraftBukkit - set bukkitYield when setting explosionpower ++ entitylargefireball.bukkitYield = entitylargefireball.explosionPower = this.ghast.getExplosionPower(); + entitylargefireball.setPos(this.ghast.getX() + vec3d.x * 4.0D, this.ghast.getY(0.5D) + 0.5D, entitylargefireball.getZ() + vec3d.z * 4.0D); + world.addFreshEntity(entitylargefireball); + this.chargeTime = -40; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch new file mode 100644 index 0000000000..078be667c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/monster/Guardian.java ++++ b/net/minecraft/world/entity/monster/Guardian.java +@@ -60,6 +60,7 @@ + private boolean clientSideTouchedGround; + @Nullable + public RandomStrollGoal randomStrollGoal; ++ public Guardian.GuardianAttackGoal guardianAttackGoal; // CraftBukkit - add field + + public Guardian(EntityType type, Level world) { + super(type, world); +@@ -75,7 +76,7 @@ + MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D); + + this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80); +- this.goalSelector.addGoal(4, new Guardian.GuardianAttackGoal(this)); ++ this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field + this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction); + this.goalSelector.addGoal(7, this.randomStrollGoal); + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch new file mode 100644 index 0000000000..0df55d8f37 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/Husk.java ++++ b/net/minecraft/world/entity/monster/Husk.java +@@ -59,7 +59,7 @@ + if (flag && this.getMainHandItem().isEmpty() && target instanceof LivingEntity) { + float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty(); + +- ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this); ++ ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + + return flag; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch new file mode 100644 index 0000000000..b839923b98 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/entity/monster/Illusioner.java ++++ b/net/minecraft/world/entity/monster/Illusioner.java +@@ -184,7 +184,17 @@ + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { ++ // Paper start - EntityShootBowEvent ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, target.getUsedItemHand(), 0.8F, true); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { + Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); ++ } ++ // Paper end - EntityShootBowEvent + } + + this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); +@@ -218,7 +228,7 @@ + + @Override + protected void performSpellCasting() { +- Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200)); ++ Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit + } + + @Nullable +@@ -269,7 +279,7 @@ + + @Override + protected void performSpellCasting() { +- Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this); ++ Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch new file mode 100644 index 0000000000..bae67fa57b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/Monster.java ++++ b/net/minecraft/world/entity/monster/Monster.java +@@ -92,7 +92,7 @@ + return false; + } else { + DimensionType dimensionType = world.dimensionType(); +- int i = dimensionType.monsterSpawnBlockLightLimit(); ++ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning + if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { + return false; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch new file mode 100644 index 0000000000..ca42f84c2e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch @@ -0,0 +1,78 @@ +--- a/net/minecraft/world/entity/monster/Phantom.java ++++ b/net/minecraft/world/entity/monster/Phantom.java +@@ -139,7 +139,7 @@ + + @Override + public void aiStep() { +- if (this.isAlive() && this.isSunBurnTick()) { ++ if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API + this.igniteForSeconds(8.0F); + } + +@@ -161,6 +161,14 @@ + } + + this.setPhantomSize(nbt.getInt("Size")); ++ // Paper start ++ if (nbt.hasUUID("Paper.SpawningEntity")) { ++ this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); ++ } ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end + } + + @Override +@@ -170,6 +178,12 @@ + nbt.putInt("AY", this.anchorPoint.getY()); + nbt.putInt("AZ", this.anchorPoint.getZ()); + nbt.putInt("Size", this.getPhantomSize()); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); ++ } ++ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); ++ // Paper end + } + + @Override +@@ -219,6 +233,20 @@ + return predicate.test(world, this, target); + } + ++ // Paper start ++ @Nullable ++ java.util.UUID spawningEntity; ++ ++ @Nullable ++ public java.util.UUID getSpawningEntity() { ++ return this.spawningEntity; ++ } ++ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Paper end ++ + private static enum AttackPhase { + + CIRCLE, SWOOP; +@@ -522,14 +550,15 @@ + List list = worldserver.getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D)); + + if (!list.isEmpty()) { +- list.sort(Comparator.comparing(Entity::getY).reversed()); ++ list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + Player entityhuman = (Player) iterator.next(); + + if (Phantom.this.canAttack(worldserver, entityhuman, TargetingConditions.DEFAULT)) { +- Phantom.this.setTarget(entityhuman); ++ if (!level().paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.IS_INSOMNIAC.test(entityhuman)) // Paper - Add phantom creative and insomniac controls ++ Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch new file mode 100644 index 0000000000..43bd8b72e6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/monster/Pillager.java ++++ b/net/minecraft/world/entity/monster/Pillager.java +@@ -52,6 +52,9 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.ServerLevelAccessor; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Pillager extends AbstractIllager implements CrossbowAttackMob, InventoryCarrier { + +@@ -206,7 +209,7 @@ + ItemStack itemstack1 = this.inventory.addItem(itemstack); + + if (itemstack1.isEmpty()) { +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } else { + itemstack.setCount(itemstack1.getCount()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch new file mode 100644 index 0000000000..1cbab8cd90 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/world/entity/monster/Ravager.java ++++ b/net/minecraft/world/entity/monster/Ravager.java +@@ -42,6 +42,9 @@ + import net.minecraft.world.level.pathfinder.PathType; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class Ravager extends Raider { + +@@ -158,12 +161,19 @@ + Block block = iblockdata.getBlock(); + + if (block instanceof LeavesBlock) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ continue; ++ } ++ // CraftBukkit end + flag = worldserver.destroyBlock(blockposition, true, this) || flag; + } + } + + if (!flag && this.onGround()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + } +@@ -281,7 +291,7 @@ + double d1 = entity.getZ() - this.getZ(); + double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D); + +- entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D); ++ entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch new file mode 100644 index 0000000000..fb73125321 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch @@ -0,0 +1,59 @@ +--- a/net/minecraft/world/entity/monster/Shulker.java ++++ b/net/minecraft/world/entity/monster/Shulker.java +@@ -59,6 +59,12 @@ + import net.minecraft.world.phys.Vec3; + import org.joml.Vector3f; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end ++ + public class Shulker extends AbstractGolem implements VariantHolder>, Enemy { + + private static final ResourceLocation COVERED_ARMOR_MODIFIER_ID = ResourceLocation.withDefaultNamespace("covered"); +@@ -283,7 +289,13 @@ + + @Override + public void stopRiding() { +- super.stopRiding(); ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ super.stopRiding(suppressCancellation); ++ // Paper end - Force entity dismount during teleportation + if (this.level().isClientSide) { + this.clientOldAttachPosition = this.blockPosition(); + } +@@ -402,6 +414,14 @@ + Direction enumdirection = this.findAttachableSurface(blockposition1); + + if (enumdirection != null) { ++ // CraftBukkit start ++ EntityTeleportEvent teleportEvent = CraftEventFactory.callEntityTeleportEvent(this, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()); ++ if (teleportEvent.isCancelled() || teleportEvent.getTo() == null) { // Paper ++ return false; ++ } else { ++ blockposition1 = CraftLocation.toBlockPosition(teleportEvent.getTo()); ++ } ++ // CraftBukkit end + this.unRide(); + this.setAttachFace(enumdirection); + this.playSound(SoundEvents.SHULKER_TELEPORT, 1.0F, 1.0F); +@@ -472,7 +492,12 @@ + if (entityshulker != null) { + entityshulker.setVariant(this.getVariant()); + entityshulker.moveTo(vec3d); +- this.level().addFreshEntity(entityshulker); ++ // Paper start - Shulker duplicate event ++ if (!new io.papermc.paper.event.entity.ShulkerDuplicateEvent((org.bukkit.entity.Shulker) entityshulker.getBukkitEntity(), (org.bukkit.entity.Shulker) this.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end - Shulker duplicate event ++ this.level().addFreshEntity(entityshulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - the mysteries of life + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch new file mode 100644 index 0000000000..b031571b09 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/entity/monster/Silverfish.java ++++ b/net/minecraft/world/entity/monster/Silverfish.java +@@ -30,6 +30,10 @@ + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.InfestedBlock; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Silverfish extends Monster { + +@@ -119,7 +123,7 @@ + } else { + Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true); + +- return entityhuman == null; ++ return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API + } + } + +@@ -160,6 +164,12 @@ + Block block = iblockdata.getBlock(); + + if (block instanceof InfestedBlock) { ++ // CraftBukkit start ++ BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state ++ continue; ++ } ++ // CraftBukkit end + if (getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + world.destroyBlock(blockposition1, true, this.silverfish); + } else { +@@ -229,9 +239,14 @@ + BlockState iblockdata = world.getBlockState(blockposition); + + if (InfestedBlock.isCompatibleHostBlock(iblockdata)) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, InfestedBlock.infestedStateByHost(iblockdata))) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(blockposition, InfestedBlock.infestedStateByHost(iblockdata), 3); + this.mob.spawnAnim(); +- this.mob.discard(); ++ this.mob.discard(EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch new file mode 100644 index 0000000000..d17fea360f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/entity/monster/Skeleton.java ++++ b/net/minecraft/world/entity/monster/Skeleton.java +@@ -94,12 +94,19 @@ + } + + protected void doFreezeConversion() { +- this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { ++ final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam + if (!this.isSilent()) { + this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0); + } + +- }); ++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);// CraftBukkit - add spawn and transform reasons ++ ++ // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam ++ if (stray == null) { ++ this.conversionTime = 300; ++ } ++ // Paper end - Fix issues with mob conversion ++ + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch new file mode 100644 index 0000000000..4ee5279ca5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch @@ -0,0 +1,239 @@ +--- a/net/minecraft/world/entity/monster/Slime.java ++++ b/net/minecraft/world/entity/monster/Slime.java +@@ -46,6 +46,14 @@ + import net.minecraft.world.level.levelgen.WorldgenRandom; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.PlayerTeam; ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.List; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++import org.bukkit.event.entity.SlimeSplitEvent; ++// CraftBukkit end + + public class Slime extends Mob implements Enemy { + +@@ -111,6 +119,7 @@ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("Paper.canWander", this.canWander); // Paper + nbt.putInt("Size", this.getSize() - 1); + nbt.putBoolean("wasOnGround", this.wasOnGround); + } +@@ -119,6 +128,11 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + this.setSize(nbt.getInt("Size") + 1, false); + super.readAdditionalSaveData(nbt); ++ // Paper start ++ if (nbt.contains("Paper.canWander")) { ++ this.canWander = nbt.getBoolean("Paper.canWander"); ++ } ++ // Paper end + this.wasOnGround = nbt.getBoolean("wasOnGround"); + } + +@@ -197,11 +211,18 @@ + + @Override + public EntityType getType() { +- return super.getType(); ++ return (EntityType) super.getType(); // CraftBukkit - decompile error + } + + @Override + public void remove(Entity.RemovalReason reason) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end + int i = this.getSize(); + + if (!this.level().isClientSide && i > 1 && this.isDeadOrDying()) { +@@ -210,19 +231,47 @@ + int j = i / 2; + int k = 2 + this.random.nextInt(3); + PlayerTeam scoreboardteam = this.getTeam(); ++ ++ // CraftBukkit start ++ SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), k); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled() && event.getCount() > 0) { ++ k = event.getCount(); ++ } else { ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause ++ return; ++ } ++ List slimes = new ArrayList<>(j); ++ // CraftBukkit end + + for (int l = 0; l < k; ++l) { + float f2 = ((float) (l % 2) - 0.5F) * f1; + float f3 = ((float) (l / 2) - 0.5F) * f1; + +- this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> { ++ Slime converted = this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> { // CraftBukkit ++ entityslime.aware = this.aware; // Paper - Fix nerfed slime when splitting + entityslime.setSize(j, true); + entityslime.moveTo(this.getX() + (double) f2, this.getY() + 0.5D, this.getZ() + (double) f3, this.random.nextFloat() * 360.0F, 0.0F); +- }); ++ // CraftBukkit start ++ }, null, null); ++ if (converted != null) { ++ slimes.add(converted); ++ } ++ // CraftBukkit end + } ++ // CraftBukkit start ++ if (CraftEventFactory.callEntityTransformEvent(this, slimes, EntityTransformEvent.TransformReason.SPLIT).isCancelled()) { ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause ++ return; ++ } ++ for (LivingEntity living : slimes) { ++ this.level().addFreshEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason ++ } ++ // CraftBukkit end + } + +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause + } + + @Override +@@ -291,7 +340,11 @@ + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + +- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper start - Replace rules for Height in Swamp Biome ++ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; ++ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; ++ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper end - Replace rules for Height in Swamp Biome + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + +@@ -300,9 +353,12 @@ + } + + ChunkPos chunkcoordintpair = new ChunkPos(pos); +- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), 987234911L).nextInt(10) == 0; ++ boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + +- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { ++ // Paper start - Replace rules for Height in Slime Chunks ++ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; ++ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { ++ // Paper end - Replace rules for Height in Slime Chunks + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + } +@@ -432,7 +488,7 @@ + + @Override + public boolean canUse() { +- return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events + } + + @Override +@@ -469,7 +525,15 @@ + public boolean canUse() { + LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl); ++ // Paper start - Slime pathfinder events ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (!this.slime.canAttack(entityliving)) { ++ return false; ++ } ++ return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end - Slime pathfinder events + } + + @Override +@@ -482,7 +546,15 @@ + public boolean canContinueToUse() { + LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : --this.growTiredTimer > 0); ++ // Paper start - Slime pathfinder events ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (!this.slime.canAttack(entityliving)) { ++ return false; ++ } ++ return --this.growTiredTimer > 0 && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end - Slime pathfinder events + } + + @Override +@@ -505,6 +577,13 @@ + } + + } ++ ++ // Paper start - Slime pathfinder events; clear timer and target when goal resets ++ public void stop() { ++ this.growTiredTimer = 0; ++ this.slime.setTarget(null); ++ } ++ // Paper end - Slime pathfinder events + } + + private static class SlimeRandomDirectionGoal extends Goal { +@@ -520,7 +599,7 @@ + + @Override + public boolean canUse() { +- return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - Slime pathfinder events + } + + @Override +@@ -528,6 +607,11 @@ + if (--this.nextRandomizeTime <= 0) { + this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60)); + this.chosenDegrees = (float) this.slime.getRandom().nextInt(360); ++ // Paper start - Slime pathfinder events ++ com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent event = new com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees); ++ if (!this.slime.canWander || !event.callEvent()) return; ++ this.chosenDegrees = event.getNewYaw(); ++ // Paper end - Slime pathfinder events + } + + MoveControl controllermove = this.slime.getMoveControl(); +@@ -550,7 +634,7 @@ + + @Override + public boolean canUse() { +- return !this.slime.isPassenger(); ++ return !this.slime.isPassenger() && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events + } + + @Override +@@ -563,4 +647,15 @@ + + } + } ++ ++ // Paper start - Slime pathfinder events ++ private boolean canWander = true; ++ public boolean canWander() { ++ return canWander; ++ } ++ ++ public void setWander(boolean canWander) { ++ this.canWander = canWander; ++ } ++ // Paper end - Slime pathfinder events + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch new file mode 100644 index 0000000000..7e02fcf3a8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/entity/monster/SpellcasterIllager.java ++++ b/net/minecraft/world/entity/monster/SpellcasterIllager.java +@@ -17,6 +17,9 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.goal.Goal; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public abstract class SpellcasterIllager extends AbstractIllager { + +@@ -159,6 +162,11 @@ + public void tick() { + --this.attackWarmupDelay; + if (this.attackWarmupDelay == 0) { ++ // CraftBukkit start ++ if (!CraftEventFactory.handleEntitySpellCastEvent(SpellcasterIllager.this, this.getSpell())) { ++ return; ++ } ++ // CraftBukkit end + this.performSpellCasting(); + SpellcasterIllager.this.playSound(SpellcasterIllager.this.getCastingSoundEvent(), 1.0F, 1.0F); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch new file mode 100644 index 0000000000..1dae9b0f57 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/entity/monster/Spider.java ++++ b/net/minecraft/world/entity/monster/Spider.java +@@ -82,7 +82,7 @@ + public void tick() { + super.tick(); + if (!this.level().isClientSide) { +- this.setClimbing(this.horizontalCollision); ++ this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border) + } + + } +@@ -126,7 +126,7 @@ + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.POISON) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + public boolean isClimbing() { +@@ -172,7 +172,7 @@ + Holder holder = entityspider_groupdataspider.effect; + + if (holder != null) { +- this.addEffect(new MobEffectInstance(holder, -1)); ++ this.addEffect(new MobEffectInstance(holder, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch new file mode 100644 index 0000000000..fbc85b5bbe --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/entity/monster/Strider.java ++++ b/net/minecraft/world/entity/monster/Strider.java +@@ -350,7 +350,14 @@ + + boolean flag2 = flag1; + +- this.setSuffocating(!flag || flag2); ++ // CraftBukkit start ++ boolean suffocating = !flag || flag2; ++ if (suffocating ^ this.isSuffocating()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callStriderTemperatureChangeEvent(this, suffocating)) { ++ this.setSuffocating(suffocating); ++ } ++ } ++ // CraftBukkit end + } + + super.tick(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch new file mode 100644 index 0000000000..c2c4e27616 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/entity/monster/Vex.java ++++ b/net/minecraft/world/entity/monster/Vex.java +@@ -354,7 +354,10 @@ + for (int i = 0; i < 3; ++i) { + BlockPos blockposition1 = blockposition.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7); + +- if (Vex.this.level().isEmptyBlock(blockposition1)) { ++ // Paper start - Don't load chunks ++ final net.minecraft.world.level.block.state.BlockState blockState = Vex.this.level().getBlockStateIfLoaded(blockposition1); ++ if (blockState != null && blockState.isAir()) { ++ // Paper end - Don't load chunks + Vex.this.moveControl.setWantedPosition((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 0.25D); + if (Vex.this.getTarget() == null) { + Vex.this.getLookControl().setLookAt((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 180.0F, 20.0F); +@@ -381,7 +384,7 @@ + + @Override + public void start() { +- Vex.this.setTarget(Vex.this.owner.getTarget()); ++ Vex.this.setTarget(Vex.this.owner.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit + super.start(); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch new file mode 100644 index 0000000000..e652ce7a7e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/Vindicator.java ++++ b/net/minecraft/world/entity/monster/Vindicator.java +@@ -184,7 +184,7 @@ + + static class VindicatorBreakDoorGoal extends BreakDoorGoal { + public VindicatorBreakDoorGoal(Mob mob) { +- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); ++ super(mob, 6, com.google.common.base.Predicates.in(mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper - Configurable door breaking difficulty + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch new file mode 100644 index 0000000000..6939fca599 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch @@ -0,0 +1,77 @@ +--- a/net/minecraft/world/entity/monster/Witch.java ++++ b/net/minecraft/world/entity/monster/Witch.java +@@ -124,9 +124,15 @@ + + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS); ++ // Paper start - WitchConsumePotionEvent ++ if (itemstack.is(Items.POTION)) { ++ com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ potioncontents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null; ++ } ++ // Paper end - WitchConsumePotionEvent + + if (itemstack.is(Items.POTION) && potioncontents != null) { +- potioncontents.forEachEffect(this::addEffect); ++ potioncontents.forEachEffect((effect) -> this.addEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK)); // CraftBukkit + } + + this.gameEvent(GameEvent.DRINK); +@@ -146,17 +152,7 @@ + } + + if (holder != null) { +- this.setItemSlot(EquipmentSlot.MAINHAND, PotionContents.createItemStack(Items.POTION, holder)); +- this.usingTime = this.getMainHandItem().getUseDuration(this); +- this.setUsingItem(true); +- if (!this.isSilent()) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); +- } +- +- AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); +- +- attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); +- attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); ++ this.setDrinkingPotion(PotionContents.createItemStack(Items.POTION, holder)); // Paper - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out + } + } + +@@ -166,7 +162,24 @@ + } + + super.aiStep(); ++ } ++ ++ // Paper start - moved to its own method ++ public void setDrinkingPotion(ItemStack potion) { ++ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); ++ this.setItemSlot(EquipmentSlot.MAINHAND, potion); ++ this.usingTime = this.getMainHandItem().getUseDuration(this); ++ this.setUsingItem(true); ++ if (!this.isSilent()) { ++ this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++ } ++ ++ AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); ++ ++ attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); ++ attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); + } ++ // Paper end + + @Override + public SoundEvent getCelebrateSound() { +@@ -231,6 +244,13 @@ + ServerLevel worldserver = (ServerLevel) world; + ItemStack itemstack = PotionContents.createItemStack(Items.SPLASH_POTION, holder); + ++ // Paper start - WitchThrowPotionEvent ++ com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ if (!event.callEvent()) { ++ return; ++ } ++ itemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); ++ // Paper end - WitchThrowPotionEvent + Projectile.spawnProjectileUsingShoot(ThrownPotion::new, worldserver, itemstack, this, d0, d1 + d3 * 0.2D, d2, 0.75F, 8.0F); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch new file mode 100644 index 0000000000..61b7e05667 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/monster/WitherSkeleton.java ++++ b/net/minecraft/world/entity/monster/WitherSkeleton.java +@@ -110,7 +110,7 @@ + return false; + } else { + if (target instanceof LivingEntity) { +- ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this); ++ ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + + return true; +@@ -127,6 +127,6 @@ + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch new file mode 100644 index 0000000000..4b95368547 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch @@ -0,0 +1,304 @@ +--- a/net/minecraft/world/entity/monster/Zombie.java ++++ b/net/minecraft/world/entity/monster/Zombie.java +@@ -6,19 +6,6 @@ + import java.util.List; + import java.util.function.Predicate; + import javax.annotation.Nullable; +-import net.minecraft.core.BlockPos; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.nbt.NbtOps; +-import net.minecraft.nbt.Tag; +-import net.minecraft.network.syncher.EntityDataAccessor; +-import net.minecraft.network.syncher.EntityDataSerializers; +-import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.resources.ResourceLocation; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.sounds.SoundEvent; +-import net.minecraft.sounds.SoundEvents; +-import net.minecraft.sounds.SoundSource; +-import net.minecraft.tags.FluidTags; + import net.minecraft.util.Mth; + import net.minecraft.util.RandomSource; + import net.minecraft.world.Difficulty; +@@ -66,11 +53,31 @@ + import net.minecraft.world.level.ServerLevelAccessor; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.network.syncher.EntityDataAccessor; ++import net.minecraft.network.syncher.EntityDataSerializers; ++import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.resources.ResourceLocation; ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundEvents; ++import net.minecraft.sounds.SoundSource; ++import net.minecraft.tags.FluidTags; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityTargetEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++// CraftBukkit end + + public class Zombie extends Monster { + + private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby"); +- private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, 0.5D, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); ++ private final AttributeModifier babyModifier = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, this.level().paperConfig().entities.behavior.babyZombieMovementModifier, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); // Paper - Make baby speed configurable + private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge"); + private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05000000074505806D, AttributeModifier.Operation.ADD_VALUE); + private static final ResourceLocation LEADER_ZOMBIE_BONUS_ID = ResourceLocation.withDefaultNamespace("leader_zombie_bonus"); +@@ -91,10 +98,12 @@ + private boolean canBreakDoors; + private int inWaterTime; + public int conversionTime; ++ // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time ++ private boolean shouldBurnInDay = true; // Paper - Add more Zombie API + + public Zombie(EntityType type, Level world) { + super(type, world); +- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); ++ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty + } + + public Zombie(Level world) { +@@ -103,7 +112,7 @@ + + @Override + protected void registerGoals() { +- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); ++ if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.addBehaviourGoals(); +@@ -115,7 +124,7 @@ + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); +- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); ++ if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); + } +@@ -165,11 +174,16 @@ + + @Override + protected int getBaseExperienceReward(ServerLevel world) { ++ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward + if (this.isBaby()) { + this.xpReward = (int) ((double) this.xpReward * 2.5D); + } + +- return super.getBaseExperienceReward(world); ++ // Paper start - store previous value to reset after calculating XP reward ++ int reward = super.getBaseExperienceReward(world); ++ this.xpReward = previousReward; ++ return reward; ++ // Paper end - store previous value to reset after calculating XP reward + } + + @Override +@@ -178,9 +192,9 @@ + if (this.level() != null && !this.level().isClientSide) { + AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); + +- attributemodifiable.removeModifier(Zombie.SPEED_MODIFIER_BABY_ID); ++ attributemodifiable.removeModifier(this.babyModifier.id()); // Paper - Make baby speed configurable + if (baby) { +- attributemodifiable.addTransientModifier(Zombie.SPEED_MODIFIER_BABY); ++ attributemodifiable.addTransientModifier(this.babyModifier); // Paper - Make baby speed configurable + } + } + +@@ -203,7 +217,7 @@ + public void tick() { + if (!this.level().isClientSide && this.isAlive() && !this.isNoAi()) { + if (this.isUnderWaterConverting()) { +- --this.conversionTime; ++ --this.conversionTime; // Paper - remove anti tick skipping measures / wall time + if (this.conversionTime < 0) { + this.doUnderWaterConversion(); + } +@@ -220,6 +234,7 @@ + } + + super.tick(); ++ // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time + } + + @Override +@@ -253,7 +268,14 @@ + super.aiStep(); + } + ++ // Paper start - Add more Zombie API ++ public void stopDrowning() { ++ this.conversionTime = -1; ++ this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false); ++ } ++ // Paper end - Add more Zombie API + public void startUnderWaterConversion(int ticksUntilWaterConversion) { ++ // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time + this.conversionTime = ticksUntilWaterConversion; + this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, true); + } +@@ -267,32 +289,51 @@ + } + + protected void convertToZombieType(EntityType entityType) { +- this.convertTo(entityType, ConversionParams.single(this, true, true), (entityzombie) -> { ++ Zombie converted = this.convertTo(entityType, ConversionParams.single(this, true, true), (entityzombie) -> { // CraftBukkit + entityzombie.handleAttributes(entityzombie.level().getCurrentDifficultyAt(entityzombie.blockPosition()).getSpecialMultiplier()); +- }); ++ // CraftBukkit start ++ }, EntityTransformEvent.TransformReason.DROWNED, CreatureSpawnEvent.SpawnReason.DROWNED); ++ if (converted == null) { ++ ((org.bukkit.entity.Zombie) this.getBukkitEntity()).setConversionTime(-1); // CraftBukkit - SPIGOT-5208: End conversion to stop event spam ++ } ++ // CraftBukkit end + } + + @VisibleForTesting + public boolean convertVillagerToZombieVillager(ServerLevel world, Villager villager) { +- ZombieVillager entityzombievillager = (ZombieVillager) villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), (entityzombievillager1) -> { +- entityzombievillager1.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombievillager1.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true)); +- entityzombievillager1.setVillagerData(villager.getVillagerData()); +- entityzombievillager1.setGossips((Tag) villager.getGossips().store(NbtOps.INSTANCE)); +- entityzombievillager1.setTradeOffers(villager.getOffers().copy()); +- entityzombievillager1.setVillagerXp(villager.getVillagerXp()); +- if (!this.isSilent()) { +- world.levelEvent((Player) null, 1026, this.blockPosition(), 0); ++ // CraftBukkit start ++ return Zombie.convertVillagerToZombieVillager(world, villager, this.blockPosition(), this.isSilent(), EntityTransformEvent.TransformReason.INFECTION, CreatureSpawnEvent.SpawnReason.INFECTION) != null; ++ } ++ ++ public static ZombieVillager convertVillagerToZombieVillager(ServerLevel worldserver, Villager entityvillager, net.minecraft.core.BlockPos blockPosition, boolean silent, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // CraftBukkit end ++ ZombieVillager entityzombievillager = (ZombieVillager) entityvillager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(entityvillager, true, true), (entityzombievillager1) -> { ++ entityzombievillager1.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityzombievillager1.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true)); ++ entityzombievillager1.setVillagerData(entityvillager.getVillagerData()); ++ entityzombievillager1.setGossips((Tag) entityvillager.getGossips().store(NbtOps.INSTANCE)); ++ entityzombievillager1.setTradeOffers(entityvillager.getOffers().copy()); ++ entityzombievillager1.setVillagerXp(entityvillager.getVillagerXp()); ++ // CraftBukkit start ++ if (!silent) { ++ worldserver.levelEvent((Player) null, 1026, blockPosition, 0); + } + +- }); ++ }, transformReason, spawnReason); + +- return entityzombievillager != null; ++ return entityzombievillager; ++ // CraftBukkit end + } + + public boolean isSunSensitive() { +- return true; ++ return this.shouldBurnInDay; // Paper - Add more Zombie API + } + ++ // Paper start - Add more Zombie API ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ this.shouldBurnInDay = shouldBurnInDay; ++ } ++ // Paper end - Add more Zombie API ++ + @Override + public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { + if (!super.hurtServer(world, source, amount)) { +@@ -323,10 +364,10 @@ + + if (SpawnPlacements.isSpawnPositionOk(entitytypes, world, blockposition) && SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.REINFORCEMENT, blockposition, world.random)) { + entityzombie.setPos((double) i1, (double) j1, (double) k1); +- if (!world.hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) { +- entityzombie.setTarget(entityliving); ++ if (!world.hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) { // Paper - affects spawning api ++ entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, (SpawnGroupData) null); +- world.addFreshEntityWithPassengers(entityzombie); ++ world.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit + AttributeInstance attributemodifiable = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE); + AttributeModifier attributemodifier = attributemodifiable.getModifier(Zombie.REINFORCEMENT_CALLER_CHARGE_ID); + double d0 = attributemodifier != null ? attributemodifier.amount() : 0.0D; +@@ -352,7 +393,14 @@ + float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty(); + + if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < f * 0.3F) { +- target.igniteForSeconds((float) (2 * (int) f)); ++ // CraftBukkit start ++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), target.getBukkitEntity(), (float) (2 * (int) f)); // PAIL: fixme ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ target.igniteForSeconds(event.getDuration(), false); ++ } ++ // CraftBukkit end + } + } + +@@ -385,7 +433,7 @@ + + @Override + public EntityType getType() { +- return super.getType(); ++ return (EntityType) super.getType(); // CraftBukkit - decompile error + } + + protected boolean canSpawnInLiquids() { +@@ -414,6 +462,7 @@ + nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); + nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); + nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); ++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API + } + + @Override +@@ -425,6 +474,11 @@ + if (nbt.contains("DrownedConversionTime", 99) && nbt.getInt("DrownedConversionTime") > -1) { + this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); + } ++ // Paper start - Add more Zombie API ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end - Add more Zombie API + + } + +@@ -432,10 +486,8 @@ + public boolean killedEntity(ServerLevel world, LivingEntity other) { + boolean flag = super.killedEntity(world, other); + +- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager entityvillager) { +- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { +- return flag; +- } ++ final double fallbackChance = world.getDifficulty() == Difficulty.HARD ? 100d : world.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection ++ if (this.random.nextDouble() * 100 < world.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && other instanceof Villager entityvillager) { // Paper - Configurable chance of villager zombie infection + + if (this.convertVillagerToZombieVillager(world, entityvillager)) { + flag = false; +@@ -468,7 +520,7 @@ + float f = difficulty.getSpecialMultiplier(); + + if (spawnReason != EntitySpawnReason.CONVERSION) { +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); ++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - Add world settings for mobs picking up loot + } + + if (object == null) { +@@ -496,7 +548,7 @@ + entitychicken1.finalizeSpawn(world, difficulty, EntitySpawnReason.JOCKEY, (SpawnGroupData) null); + entitychicken1.setChickenJockey(true); + this.startRiding(entitychicken1); +- world.addFreshEntity(entitychicken1); ++ world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch new file mode 100644 index 0000000000..d025644b62 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch @@ -0,0 +1,149 @@ +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -18,10 +18,6 @@ + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.level.ServerPlayer; +-import net.minecraft.sounds.SoundEvent; +-import net.minecraft.sounds.SoundEvents; + import net.minecraft.world.DifficultyInstance; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; +@@ -35,6 +31,7 @@ + import net.minecraft.world.entity.SlotAccess; + import net.minecraft.world.entity.SpawnGroupData; + import net.minecraft.world.entity.ai.village.ReputationEventType; ++import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.npc.VillagerData; + import net.minecraft.world.entity.npc.VillagerDataHolder; + import net.minecraft.world.entity.npc.VillagerProfession; +@@ -52,6 +49,16 @@ + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundEvents; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++// CraftBukkit end ++ + public class ZombieVillager extends Zombie implements VillagerDataHolder { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -69,6 +76,7 @@ + @Nullable + private MerchantOffers tradeOffers; + private int villagerXp; ++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field + + public ZombieVillager(EntityType type, Level world) { + super(type, world); +@@ -87,7 +95,7 @@ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); +- DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); ++ DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); // CraftBukkit - decompile error + Logger logger = ZombieVillager.LOGGER; + + Objects.requireNonNull(logger); +@@ -122,7 +130,7 @@ + } + + if (nbt.contains("Offers")) { +- DataResult dataresult1 = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); ++ DataResult dataresult1 = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); // CraftBukkit - decompile error + Logger logger1 = ZombieVillager.LOGGER; + + Objects.requireNonNull(logger1); +@@ -149,6 +157,10 @@ + public void tick() { + if (!this.level().isClientSide && this.isAlive() && this.isConverting()) { + int i = this.getConversionProgress(); ++ // CraftBukkit start - Use wall time instead of ticks for villager conversion ++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick; ++ i *= elapsedTicks; ++ // CraftBukkit end + + this.villagerConversionTime -= i; + if (this.villagerConversionTime <= 0) { +@@ -157,6 +169,7 @@ + } + + super.tick(); ++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit + } + + @Override +@@ -194,12 +207,20 @@ + } + + public void startConverting(@Nullable UUID uuid, int delay) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.startConverting(uuid, delay, true); ++ } ++ ++ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + this.conversionStarter = uuid; + this.villagerConversionTime = delay; + this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); +- this.removeEffect(MobEffects.WEAKNESS); +- this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0))); +- this.level().broadcastEntityEvent(this, (byte) 16); ++ // CraftBukkit start ++ this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); ++ this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); ++ // CraftBukkit end ++ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event + } + + @Override +@@ -215,10 +236,11 @@ + } + + private void finishConversion(ServerLevel world) { +- this.convertTo(EntityType.VILLAGER, ConversionParams.single(this, false, false), (entityvillager) -> { ++ Villager converted = this.convertTo(EntityType.VILLAGER, ConversionParams.single(this, false, false), (entityvillager) -> { // CraftBukkit + Iterator iterator = this.dropPreservedEquipment(world, (itemstack) -> { + return !EnchantmentHelper.has(itemstack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE); + }).iterator(); ++ this.forceDrops = false; // CraftBukkit + + while (iterator.hasNext()) { + EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next(); +@@ -240,7 +262,7 @@ + entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); + entityvillager.refreshBrain(world); + if (this.conversionStarter != null) { +- Player entityhuman = world.getPlayerByUUID(this.conversionStarter); ++ Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate + + if (entityhuman instanceof ServerPlayer) { + CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager); +@@ -248,12 +270,16 @@ + } + } + +- entityvillager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); ++ entityvillager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); // CraftBukkit + if (!this.isSilent()) { + world.levelEvent((Player) null, 1027, this.blockPosition(), 0); + } +- +- }); ++ // CraftBukkit start ++ }, EntityTransformEvent.TransformReason.CURED, CreatureSpawnEvent.SpawnReason.CURED); ++ if (converted == null) { ++ ((org.bukkit.entity.ZombieVillager) this.getBukkitEntity()).setConversionTime(-1); // SPIGOT-5208: End conversion to stop event spam ++ } ++ // CraftBukkit end + } + + @VisibleForTesting diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch new file mode 100644 index 0000000000..0b3d7e043c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch @@ -0,0 +1,67 @@ +--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -56,6 +56,7 @@ + private static final int ALERT_RANGE_Y = 10; + private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); + private int ticksUntilNextAlert; ++ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation + + public ZombifiedPiglin(EntityType type, Level world) { + super(type, world); +@@ -71,7 +72,7 @@ + protected void addBehaviourGoals() { + this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); +- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); ++ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); + this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); + } +@@ -149,7 +150,7 @@ + }).filter((entitypigzombie) -> { + return !entitypigzombie.isAlliedTo((Entity) this.getTarget()); + }).forEach((entitypigzombie) -> { +- entitypigzombie.setTarget(this.getTarget()); ++ entitypigzombie.setTarget(this.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit + }); + } + +@@ -158,22 +159,32 @@ + } + + @Override +- public void setTarget(@Nullable LivingEntity target) { +- if (this.getTarget() == null && target != null) { ++ public boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { // CraftBukkit - signature ++ if (this.getTarget() == null && entityliving != null) { + this.playFirstAngerSoundIn = ZombifiedPiglin.FIRST_ANGER_SOUND_DELAY.sample(this.random); + this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); + } + +- if (target instanceof Player) { +- this.setLastHurtByPlayer((Player) target); ++ if (entityliving instanceof Player) { ++ this.setLastHurtByPlayer((Player) entityliving); + } + +- super.setTarget(target); ++ return super.setTarget(entityliving, reason, fireEvent); // CraftBukkit + } + + @Override + public void startPersistentAngerTimer() { +- this.setRemainingPersistentAngerTime(ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random)); ++ // CraftBukkit start ++ Entity entity = ((ServerLevel) this.level()).getEntity(this.getPersistentAngerTarget()); ++ org.bukkit.event.entity.PigZombieAngerEvent event = new org.bukkit.event.entity.PigZombieAngerEvent((org.bukkit.entity.PigZombie) this.getBukkitEntity(), (entity == null) ? null : entity.getBukkitEntity(), ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random)); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.setPersistentAngerTarget(null); ++ pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation ++ return; ++ } ++ this.setRemainingPersistentAngerTime(event.getNewAnger()); ++ // CraftBukkit end + } + + public static boolean checkZombifiedPiglinSpawnRules(EntityType type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch new file mode 100644 index 0000000000..b3c69c8fbb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/entity/monster/breeze/Breeze.java ++++ b/net/minecraft/world/entity/monster/breeze/Breeze.java +@@ -77,7 +77,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -252,6 +252,7 @@ + + @Override + public boolean canAttackType(EntityType type) { ++ if (this.getTarget() != null) return this.getTarget().getType() == type; // SPIGOT-7957: Allow attack if target from brain was set + return type == EntityType.PLAYER || type == EntityType.IRON_GOLEM; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch new file mode 100644 index 0000000000..d3d16a6b28 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/world/entity/monster/creaking/Creaking.java ++++ b/net/minecraft/world/entity/monster/creaking/Creaking.java +@@ -198,15 +198,15 @@ + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param + if (this.canMove()) { +- super.push(deltaX, deltaY, deltaZ); ++ super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param + } + } + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -329,7 +329,7 @@ + } + + this.makeSound(this.getDeathSound()); +- this.remove(Entity.RemovalReason.DISCARDED); ++ this.remove(Entity.RemovalReason.DISCARDED, null); // CraftBukkit - add Bukkit remove cause + } + + public void creakingDeathEffects(DamageSource damageSource) { +@@ -476,7 +476,7 @@ + + @Override + protected SoundEvent getHurtSound(DamageSource source) { +- return this.isHeartBound() ? SoundEvents.CREAKING_SWAY : super.getHurtSound(source); ++ return SoundEvents.CREAKING_SWAY; + } + + @Override +@@ -502,9 +502,9 @@ + } + + @Override +- public void knockback(double strength, double x, double z) { ++ public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events + if (this.canMove()) { +- super.knockback(strength, x, z); ++ super.knockback(strength, x, z, attacker, cause); // Paper - knockback events + } + } + +@@ -549,7 +549,7 @@ + } + + public void activate(Player player) { +- this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, (Object) player); ++ this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, player); // CraftBukkit - decompile error + this.gameEvent(GameEvent.ENTITY_ACTION); + this.makeSound(SoundEvents.CREAKING_ACTIVATE); + this.setIsActive(true); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch new file mode 100644 index 0000000000..345420cb17 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch @@ -0,0 +1,48 @@ +--- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java ++++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java +@@ -63,7 +63,8 @@ + public int timeInOverworld; + public boolean cannotBeHunted; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ADULT, SensorType.HOGLIN_SPECIFIC_SENSOR); +- protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED, MemoryModuleType.IS_PANICKING}); ++ // CraftBukkit - decompile error ++ protected static final ImmutableList> MEMORY_TYPES = ImmutableList.>of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED, MemoryModuleType.IS_PANICKING}); + + public Hoglin(EntityType type, Level world) { + super(type, world); +@@ -134,7 +135,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -240,9 +241,15 @@ + } + + private void finishConversion() { +- this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> { ++ net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> { + entityzoglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); +- }); ++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons ++ ++ // Paper start - Fix issues with mob conversion; reset to prevent event spam ++ if (converted == null) { ++ this.timeInOverworld = 0; ++ } ++ // Paper end - Fix issues with mob conversion + } + + @Override +@@ -326,7 +333,7 @@ + + @Override + protected SoundEvent getAmbientSound() { +- return this.level().isClientSide ? null : (SoundEvent) HoglinAi.getSoundForCurrentActivity(this).orElse((Object) null); ++ return this.level().isClientSide ? null : (SoundEvent) HoglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - decompile error + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch new file mode 100644 index 0000000000..634a383e72 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/hoglin/HoglinBase.java ++++ b/net/minecraft/world/entity/monster/hoglin/HoglinBase.java +@@ -45,7 +45,7 @@ + double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F); + Vec3 vec3 = new Vec3(g, 0.0, h).normalize().scale(j).yRot(i); + double k = f * (double)attacker.level().random.nextFloat() * 0.5; +- target.push(vec3.x, k, vec3.z); ++ target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + target.hurtMarked = true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch new file mode 100644 index 0000000000..68e189739c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java ++++ b/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java +@@ -100,9 +100,15 @@ + } + + protected void finishConversion(ServerLevel world) { +- this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { ++ net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { // Paper - Fix issues with mob conversion; reset to prevent event spam + entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); +- }); ++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons ++ ++ // Paper start - Fix issues with mob conversion; reset to prevent event spam ++ if (converted == null) { ++ this.timeInOverworld = 0; ++ } ++ // Paper end - Fix issues with mob conversion + } + + public boolean isAdult() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch new file mode 100644 index 0000000000..d238fb349f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch @@ -0,0 +1,140 @@ +--- a/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -4,15 +4,6 @@ + import com.mojang.serialization.Dynamic; + import java.util.List; + import javax.annotation.Nullable; +-import net.minecraft.core.BlockPos; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.network.syncher.EntityDataAccessor; +-import net.minecraft.network.syncher.EntityDataSerializers; +-import net.minecraft.network.syncher.SynchedEntityData; +-import net.minecraft.resources.ResourceLocation; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.sounds.SoundEvent; +-import net.minecraft.sounds.SoundEvents; + import net.minecraft.tags.ItemTags; + import net.minecraft.tags.TagKey; + import net.minecraft.util.RandomSource; +@@ -59,6 +50,25 @@ + import net.minecraft.world.level.ServerLevelAccessor; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import java.util.stream.Collectors; ++import java.util.HashSet; ++import java.util.Set; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++import net.minecraft.network.syncher.EntityDataAccessor; ++import net.minecraft.network.syncher.EntityDataSerializers; ++import net.minecraft.network.syncher.SynchedEntityData; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundEvents; ++import net.minecraft.world.item.Item; ++// CraftBukkit end + + public class Piglin extends AbstractPiglin implements CrossbowAttackMob, InventoryCarrier { + +@@ -79,6 +89,10 @@ + public boolean cannotHunt; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ITEMS, SensorType.HURT_BY, SensorType.PIGLIN_SPECIFIC_SENSOR); + protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.DOORS_TO_CLOSE, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS, MemoryModuleType.NEARBY_ADULT_PIGLINS, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, new MemoryModuleType[]{MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.PATH, MemoryModuleType.ANGRY_AT, MemoryModuleType.UNIVERSAL_ANGER, MemoryModuleType.AVOID_TARGET, MemoryModuleType.ADMIRING_ITEM, MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM, MemoryModuleType.ADMIRING_DISABLED, MemoryModuleType.DISABLE_WALK_TO_ADMIRE_ITEM, MemoryModuleType.CELEBRATE_LOCATION, MemoryModuleType.DANCING, MemoryModuleType.HUNTED_RECENTLY, MemoryModuleType.NEAREST_VISIBLE_BABY_HOGLIN, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, MemoryModuleType.RIDE_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_HUNTABLE_HOGLIN, MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD, MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM, MemoryModuleType.ATE_RECENTLY, MemoryModuleType.NEAREST_REPELLENT}); ++ // CraftBukkit start - Custom bartering and interest list ++ public Set allowedBarterItems = new HashSet<>(); ++ public Set interestItems = new HashSet<>(); ++ // CraftBukkit end + + public Piglin(EntityType type, Level world) { + super(type, world); +@@ -97,6 +111,14 @@ + } + + this.writeInventoryToTag(nbt, this.registryAccess()); ++ // CraftBukkit start ++ ListTag barterList = new ListTag(); ++ this.allowedBarterItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(barterList::add); ++ nbt.put("Bukkit.BarterList", barterList); ++ ListTag interestList = new ListTag(); ++ this.interestItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(interestList::add); ++ nbt.put("Bukkit.InterestList", interestList); ++ // CraftBukkit end + } + + @Override +@@ -105,6 +127,10 @@ + this.setBaby(nbt.getBoolean("IsBaby")); + this.setCannotHunt(nbt.getBoolean("CannotHunt")); + this.readInventoryFromTag(nbt, this.registryAccess()); ++ // CraftBukkit start ++ this.allowedBarterItems = nbt.getList("Bukkit.BarterList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new)); ++ this.interestItems = nbt.getList("Bukkit.InterestList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new)); ++ // CraftBukkit end + } + + @VisibleForDebug +@@ -224,7 +250,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - Decompile error + } + + @Override +@@ -300,9 +326,11 @@ + @Override + protected void finishConversion(ServerLevel world) { + PiglinAi.cancelAdmiring(world, this); ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.inventory.removeAllItems().forEach((itemstack) -> { + this.spawnAtLocation(world, itemstack); + }); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + super.finishConversion(world); + } + +@@ -374,7 +402,7 @@ + } + + protected void holdInOffHand(ItemStack stack) { +- if (stack.is(PiglinAi.BARTERING_ITEM)) { ++ if (stack.is(PiglinAi.BARTERING_ITEM) || this.allowedBarterItems.contains(stack.getItem())) { // CraftBukkit - Changes to accept custom payment items + this.setItemSlot(EquipmentSlot.OFFHAND, stack); + this.setGuaranteedDrop(EquipmentSlot.OFFHAND); + } else { +@@ -401,8 +429,8 @@ + return false; + } else { + TagKey tagkey = this.getPreferredWeaponType(); +- boolean flag = PiglinAi.isLovedItem(newStack) || tagkey != null && newStack.is(tagkey); +- boolean flag1 = PiglinAi.isLovedItem(currentStack) || tagkey != null && currentStack.is(tagkey); ++ boolean flag = PiglinAi.isLovedItem(newStack, this) || tagkey != null && newStack.is(tagkey); // CraftBukkit ++ boolean flag1 = PiglinAi.isLovedItem(currentStack, this) || tagkey != null && currentStack.is(tagkey); // CraftBukkit + + return flag && !flag1 ? true : (!flag && flag1 ? false : super.canReplaceCurrentItem(newStack, currentStack, slot)); + } +@@ -410,7 +438,7 @@ + + @Override + protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) { +- this.onItemPickup(itemEntity); ++ // this.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; call in PiglinAi#pickUpItem after EntityPickupItemEvent is fired + PiglinAi.pickUpItem(world, this, itemEntity); + } + +@@ -431,7 +459,7 @@ + + @Override + protected SoundEvent getAmbientSound() { +- return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse((Object) null); ++ return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - Decompile error + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch new file mode 100644 index 0000000000..66919128bd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch @@ -0,0 +1,210 @@ +--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -71,6 +71,13 @@ + import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import java.util.stream.Collectors; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.PiglinBarterEvent; ++// CraftBukkit end + + public class PiglinAi { + +@@ -166,7 +173,8 @@ + } + + private static void initRideHoglinActivity(Brain brain) { +- brain.addActivityAndRemoveMemoryWhenStopped(Activity.RIDE, 10, ImmutableList.of(Mount.create(0.8F), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 8.0F), BehaviorBuilder.sequence(BehaviorBuilder.triggerIf(Entity::isPassenger), TriggerGate.triggerOneShuffled(ImmutableList.builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(BehaviorBuilder.triggerIf((entitypiglin) -> { ++ // CraftBukkit - decompile error ++ brain.addActivityAndRemoveMemoryWhenStopped(Activity.RIDE, 10, ImmutableList.of(Mount.create(0.8F), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 8.0F), BehaviorBuilder.sequence(BehaviorBuilder.triggerIf(Entity::isPassenger), TriggerGate.triggerOneShuffled(ImmutableList., Integer>>builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(BehaviorBuilder.triggerIf((entitypiglin) -> { + return true; + }), 1)).build())), DismountOrSkipMounting.create(8, PiglinAi::wantsToStopRiding)), MemoryModuleType.RIDE_TARGET); + } +@@ -176,7 +184,7 @@ + } + + private static RunOne createIdleLookBehaviors() { +- return new RunOne<>(ImmutableList.builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(new DoNothing(30, 60), 1)).build()); ++ return new RunOne<>(ImmutableList., Integer>>builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(new DoNothing(30, 60), 1)).build()); // CraftBukkit - decompile error + } + + private static RunOne createIdleMovementBehaviors() { +@@ -197,13 +205,13 @@ + + protected static void updateActivity(Piglin piglin) { + Brain behaviorcontroller = piglin.getBrain(); +- Activity activity = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse((Object) null); ++ Activity activity = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse(null); // CraftBukkit - decompile error + + behaviorcontroller.setActiveActivityToFirstValid(ImmutableList.of(Activity.ADMIRE_ITEM, Activity.FIGHT, Activity.AVOID, Activity.CELEBRATE, Activity.RIDE, Activity.IDLE)); +- Activity activity1 = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse((Object) null); ++ Activity activity1 = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse(null); // CraftBukkit - decompile error + + if (activity != activity1) { +- Optional optional = PiglinAi.getSoundForCurrentActivity(piglin); ++ Optional optional = PiglinAi.getSoundForCurrentActivity(piglin); // CraftBukkit - decompile error + + Objects.requireNonNull(piglin); + optional.ifPresent(piglin::makeSound); +@@ -235,23 +243,32 @@ + PiglinAi.stopWalking(piglin); + ItemStack itemstack; + +- if (itemEntity.getItem().is(Items.GOLD_NUGGET)) { +- piglin.take(itemEntity, itemEntity.getItem().getCount()); +- itemstack = itemEntity.getItem(); +- itemEntity.discard(); +- } else { ++ // CraftBukkit start ++ // Paper start - EntityPickupItemEvent fixes; fix event firing twice ++ if (itemEntity.getItem().is(Items.GOLD_NUGGET)/* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()*/) { // Paper ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) return; ++ piglin.onItemPickup(itemEntity); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification ++ // Paper end ++ piglin.take(itemEntity, itemEntity.getItem().getCount()); ++ itemstack = itemEntity.getItem(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause ++ } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, itemEntity.getItem().getCount() - 1, false).isCancelled()) { ++ piglin.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; moved from Piglin#pickUpItem - call prior to item entity modification + piglin.take(itemEntity, 1); + itemstack = PiglinAi.removeOneItemFromItemEntity(itemEntity); ++ } else { ++ return; + } ++ // CraftBukkit end + +- if (PiglinAi.isLovedItem(itemstack)) { ++ if (PiglinAi.isLovedItem(itemstack, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering + piglin.getBrain().eraseMemory(MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM); + PiglinAi.holdInOffhand(world, piglin, itemstack); + PiglinAi.admireGoldItem(piglin); + } else if (PiglinAi.isFood(itemstack) && !PiglinAi.hasEatenRecently(piglin)) { + PiglinAi.eat(piglin); + } else { +- boolean flag = !piglin.equipItemIfPossible(world, itemstack).equals(ItemStack.EMPTY); ++ boolean flag = !piglin.equipItemIfPossible(world, itemstack, null).equals(ItemStack.EMPTY); // CraftBukkit // Paper - pass null item entity to prevent duplicate pickup item event call - called above. + + if (!flag) { + PiglinAi.putInInventory(piglin, itemstack); +@@ -261,7 +278,9 @@ + + private static void holdInOffhand(ServerLevel world, Piglin piglin, ItemStack stack) { + if (PiglinAi.isHoldingItemInOffHand(piglin)) { ++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles + piglin.spawnAtLocation(world, piglin.getItemInHand(InteractionHand.OFF_HAND)); ++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + piglin.holdInOffHand(stack); +@@ -272,7 +291,7 @@ + ItemStack itemstack1 = itemstack.split(1); + + if (itemstack.isEmpty()) { +- stack.discard(); ++ stack.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } else { + stack.setItem(itemstack); + } +@@ -287,9 +306,14 @@ + boolean flag1; + + if (piglin.isAdult()) { +- flag1 = PiglinAi.isBarterCurrency(itemstack); ++ flag1 = PiglinAi.isBarterCurrency(itemstack, piglin); // CraftBukkit - Changes to allow custom payment for bartering + if (barter && flag1) { +- PiglinAi.throwItems(piglin, PiglinAi.getBarterResponseItems(piglin)); ++ // CraftBukkit start ++ PiglinBarterEvent event = CraftEventFactory.callPiglinBarterEvent(piglin, PiglinAi.getBarterResponseItems(piglin), itemstack); ++ if (!event.isCancelled()) { ++ PiglinAi.throwItems(piglin, event.getOutcome().stream().map(CraftItemStack::asNMSCopy).collect(Collectors.toList())); ++ } ++ // CraftBukkit end + } else if (!flag1) { + boolean flag2 = !piglin.equipItemIfPossible(world, itemstack).isEmpty(); + +@@ -302,7 +326,7 @@ + if (!flag1) { + ItemStack itemstack1 = piglin.getMainHandItem(); + +- if (PiglinAi.isLovedItem(itemstack1)) { ++ if (PiglinAi.isLovedItem(itemstack1, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering + PiglinAi.putInInventory(piglin, itemstack1); + } else { + PiglinAi.throwItems(piglin, Collections.singletonList(itemstack1)); +@@ -316,7 +340,9 @@ + + protected static void cancelAdmiring(ServerLevel world, Piglin piglin) { + if (PiglinAi.isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) { ++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles + piglin.spawnAtLocation(world, piglin.getOffhandItem()); ++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles + piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); + } + +@@ -379,15 +405,21 @@ + return false; + } else if (PiglinAi.isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) { + return false; +- } else if (PiglinAi.isBarterCurrency(stack)) { ++ } else if (PiglinAi.isBarterCurrency(stack, piglin)) { // CraftBukkit + return PiglinAi.isNotHoldingLovedItemInOffHand(piglin); + } else { + boolean flag = piglin.canAddToInventory(stack); + +- return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); ++ return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack, piglin) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); // Paper - upstream missed isLovedItem check + } + } + ++ // CraftBukkit start - Added method to allow checking for custom payment items ++ protected static boolean isLovedItem(ItemStack itemstack, Piglin piglin) { ++ return PiglinAi.isLovedItem(itemstack) || (piglin.interestItems.contains(itemstack.getItem()) || piglin.allowedBarterItems.contains(itemstack.getItem())); ++ } ++ // CraftBukkit end ++ + protected static boolean isLovedItem(ItemStack stack) { + return stack.is(ItemTags.PIGLIN_LOVED); + } +@@ -451,6 +483,7 @@ + } + + public static void angerNearbyPiglins(ServerLevel world, Player player, boolean blockOpen) { ++ if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests + List list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D)); + + list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> { +@@ -481,7 +514,7 @@ + } + + protected static boolean canAdmire(Piglin piglin, ItemStack nearbyItems) { +- return !PiglinAi.isAdmiringDisabled(piglin) && !PiglinAi.isAdmiringItem(piglin) && piglin.isAdult() && PiglinAi.isBarterCurrency(nearbyItems); ++ return !PiglinAi.isAdmiringDisabled(piglin) && !PiglinAi.isAdmiringItem(piglin) && piglin.isAdult() && PiglinAi.isBarterCurrency(nearbyItems, piglin); // CraftBukkit + } + + protected static void wasHurtBy(ServerLevel world, Piglin piglin, LivingEntity attacker) { +@@ -735,6 +768,12 @@ + return entity.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_ITEM); + } + ++ // CraftBukkit start - Changes to allow custom payment for bartering ++ private static boolean isBarterCurrency(ItemStack itemstack, Piglin piglin) { ++ return PiglinAi.isBarterCurrency(itemstack) || piglin.allowedBarterItems.contains(itemstack.getItem()); ++ } ++ // CraftBukkit end ++ + private static boolean isBarterCurrency(ItemStack stack) { + return stack.is(PiglinAi.BARTERING_ITEM); + } +@@ -772,7 +811,7 @@ + } + + private static boolean isNotHoldingLovedItemInOffHand(Piglin piglin) { +- return piglin.getOffhandItem().isEmpty() || !PiglinAi.isLovedItem(piglin.getOffhandItem()); ++ return piglin.getOffhandItem().isEmpty() || !PiglinAi.isLovedItem(piglin.getOffhandItem(), piglin); // CraftBukkit - Changes to allow custom payment for bartering + } + + public static boolean isZombified(EntityType entityType) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch new file mode 100644 index 0000000000..f65a699a17 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/monster/warden/AngerManagement.java ++++ b/net/minecraft/world/entity/monster/warden/AngerManagement.java +@@ -146,7 +146,7 @@ + + public int increaseAnger(Entity entity, int amount) { + boolean bl = !this.angerBySuspect.containsKey(entity); +- int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); ++ int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); // Paper - diff on change (Warden#increaseAngerAt WardenAngerChangeEvent) + if (bl) { + int j = this.angerByUuid.removeInt(entity.getUUID()); + i += j; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch new file mode 100644 index 0000000000..c6378460b2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch @@ -0,0 +1,59 @@ +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -375,7 +375,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -412,7 +412,7 @@ + public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { + MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false); + +- MobEffectUtil.addEffectToPlayersAround(world, entity, pos, (double) range, mobeffect, 200); ++ MobEffectUtil.addEffectToPlayersAround(world, entity, pos, range, mobeffect, 200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WARDEN); // CraftBukkit - Add EntityPotionEffectEvent.Cause + } + + @Override +@@ -482,6 +482,15 @@ + @VisibleForTesting + public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) { + if (!this.isNoAi() && this.canTargetEntity(entity)) { ++ // Paper start - Add WardenAngerChangeEvent ++ int activeAnger = this.angerManagement.getActiveAnger(entity); ++ io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount)); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ amount = event.getNewAnger() - activeAnger; ++ // Paper end - Add WardenAngerChangeEvent + WardenAi.setDigCooldown(this); + boolean flag1 = !(this.getTarget() instanceof Player); + int j = this.angerManagement.increaseAnger(entity, amount); +@@ -547,7 +556,7 @@ + + public void setAttackTarget(LivingEntity target) { + this.getBrain().eraseMemory(MemoryModuleType.ROAR_TARGET); +- this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, (Object) target); ++ this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, target); // CraftBukkit - decompile error + this.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); + SonicBoom.setCooldown(this, 200); + } +@@ -582,11 +591,11 @@ + + @Override + protected PathNavigation createNavigation(Level world) { +- return new GroundPathNavigation(this, this, world) { ++ return new GroundPathNavigation(this, world) { // CraftBukkit - decompile error + @Override + protected PathFinder createPathFinder(int range) { + this.nodeEvaluator = new WalkNodeEvaluator(); +- return new PathFinder(this, this.nodeEvaluator, range) { ++ return new PathFinder(this.nodeEvaluator, range) { // CraftBukkit - decompile error + @Override + protected float distance(Node a, Node b) { + return a.distanceToXZ(b); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch new file mode 100644 index 0000000000..ba7ad87dd7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch @@ -0,0 +1,106 @@ +--- a/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -40,8 +40,21 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.inventory.CraftMerchant; ++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; ++import org.bukkit.event.entity.VillagerAcquireTradeEvent; ++// CraftBukkit end ++ + public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { + ++ // CraftBukkit start ++ @Override ++ public CraftMerchant getCraftMerchant() { ++ return (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity(); ++ } ++ // CraftBukkit end + private static final EntityDataAccessor DATA_UNHAPPY_COUNTER = SynchedEntityData.defineId(AbstractVillager.class, EntityDataSerializers.INT); + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int VILLAGER_SLOT_OFFSET = 300; +@@ -50,7 +63,7 @@ + private Player tradingPlayer; + @Nullable + protected MerchantOffers offers; +- private final SimpleContainer inventory = new SimpleContainer(8); ++ private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit add argument + + public AbstractVillager(EntityType type, Level world) { + super(type, world); +@@ -101,6 +114,13 @@ + return this.tradingPlayer != null; + } + ++ // Paper start - Villager#resetOffers ++ public void resetOffers() { ++ this.offers = new MerchantOffers(); ++ this.updateTrades(); ++ } ++ // Paper end - Villager#resetOffers ++ + @Override + public MerchantOffers getOffers() { + if (this.level().isClientSide) { +@@ -121,11 +141,24 @@ + @Override + public void overrideXp(int experience) {} + ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent + @Override +- public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); ++ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ if (event == null || event.willIncreaseTradeUses()) { ++ recipe.increaseUses(); ++ } ++ if (event == null || event.isRewardingExp()) { ++ this.rewardTradeXp(recipe); ++ } ++ this.notifyTrade(recipe); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent ++ ++ @Override ++ public void notifyTrade(MerchantOffer offer) { ++ // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + this.ambientSoundTime = -this.getAmbientSoundInterval(); +- this.rewardTradeXp(offer); ++ // this.rewardTradeXp(offer); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + if (this.tradingPlayer instanceof ServerPlayer) { + CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); + } +@@ -179,7 +212,7 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + if (nbt.contains("Offers")) { +- DataResult dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); ++ DataResult dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); // CraftBukkit - decompile error + Logger logger = AbstractVillager.LOGGER; + + Objects.requireNonNull(logger); +@@ -246,7 +279,20 @@ + MerchantOffer merchantrecipe = ((VillagerTrades.ItemListing) arraylist.remove(this.random.nextInt(arraylist.size()))).getOffer(this, this.random); + + if (merchantrecipe != null) { +- recipeList.add(merchantrecipe); ++ // CraftBukkit start ++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (!event.isCancelled()) { ++ // Paper start - Fix crash from invalid ingredient list ++ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); ++ if (craftMerchantRecipe.getIngredients().isEmpty()) return; ++ recipeList.add(craftMerchantRecipe.toMinecraft()); ++ // Paper end - Fix crash from invalid ingredient list ++ } ++ // CraftBukkit end + ++j; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch new file mode 100644 index 0000000000..6d93d621f8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/net/minecraft/world/entity/npc/CatSpawner.java +@@ -82,8 +82,8 @@ + if (cat == null) { + return 0; + } else { ++ cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 + cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null); +- cat.moveTo(pos, 0.0F, 0.0F); + world.addFreshEntityWithPassengers(cat); + return 1; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch new file mode 100644 index 0000000000..d6c445bea3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/entity/npc/InventoryCarrier.java ++++ b/net/minecraft/world/entity/npc/InventoryCarrier.java +@@ -8,6 +8,10 @@ + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end ++ + public interface InventoryCarrier { + + String TAG_INVENTORY = "Inventory"; +@@ -25,13 +29,20 @@ + return; + } + ++ // CraftBukkit start ++ ItemStack remaining = new SimpleContainer(inventorysubcontainer).addItem(itemstack); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(entity, item, remaining.getCount(), false).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ + entity.onItemPickup(item); + int i = itemstack.getCount(); + ItemStack itemstack1 = inventorysubcontainer.addItem(itemstack); + + entity.take(item, i - itemstack1.getCount()); + if (itemstack1.isEmpty()) { +- item.discard(); ++ item.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } else { + itemstack.setCount(itemstack1.getCount()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch new file mode 100644 index 0000000000..cb4983a642 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch @@ -0,0 +1,211 @@ +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -93,6 +93,14 @@ + import net.minecraft.world.phys.AABB; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++import org.bukkit.event.entity.VillagerReplenishTradeEvent; ++// CraftBukkit end ++ + public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -150,7 +158,7 @@ + + @Override + public Brain getBrain() { +- return super.getBrain(); ++ return (Brain) super.getBrain(); // CraftBukkit - decompile error + } + + @Override +@@ -216,7 +224,18 @@ + return this.assignProfessionWhenSpawned; + } + ++ // Spigot Start + @Override ++ public void inactiveTick() { ++ // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( ++ if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { ++ this.customServerAiStep((ServerLevel) this.level()); ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ ++ @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + +@@ -235,7 +254,7 @@ + this.increaseProfessionLevelOnUpdate = false; + } + +- this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0)); ++ this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit + } + } + +@@ -360,7 +379,13 @@ + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); + +- merchantrecipe.resetUses(); ++ // CraftBukkit start ++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ merchantrecipe.resetUses(); ++ } ++ // CraftBukkit end + } + + this.resendOffersToTradingPlayer(); +@@ -429,7 +454,13 @@ + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); + +- merchantrecipe.resetUses(); ++ // CraftBukkit start ++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ merchantrecipe.resetUses(); ++ } ++ // CraftBukkit end + } + } + +@@ -459,6 +490,7 @@ + + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); ++ if (merchantrecipe.ignoreDiscounts) continue; // Paper - Add ignore discounts API + + merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); + } +@@ -471,6 +503,7 @@ + + while (iterator1.hasNext()) { + MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); ++ if (merchantrecipe1.ignoreDiscounts) continue; // Paper - Add ignore discounts API + double d0 = 0.3D + 0.0625D * (double) j; + int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); + +@@ -489,7 +522,7 @@ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); +- DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); ++ DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); // CraftBukkit - decompile error + Logger logger = Villager.LOGGER; + + Objects.requireNonNull(logger); +@@ -512,7 +545,7 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + if (nbt.contains("VillagerData", 10)) { +- DataResult dataresult = VillagerData.CODEC.parse(NbtOps.INSTANCE, nbt.get("VillagerData")); ++ DataResult dataresult = VillagerData.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("VillagerData"))); + Logger logger = Villager.LOGGER; + + Objects.requireNonNull(logger); +@@ -599,7 +632,7 @@ + } + + if (offer.shouldRewardExp()) { +- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +@@ -618,7 +651,7 @@ + + @Override + public void die(DamageSource damageSource) { +- Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString()); ++ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString()); // Spigot + Entity entity = damageSource.getEntity(); + + if (entity != null) { +@@ -803,12 +836,19 @@ + @Override + public void thunderHit(ServerLevel world, LightningBolt lightning) { + if (world.getDifficulty() != Difficulty.PEACEFUL) { +- Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); ++ // Paper - Add EntityZapEvent; move log down, event can cancel + Witch entitywitch = (Witch) this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), (entitywitch1) -> { ++ // Paper start - Add EntityZapEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch1).isCancelled()) { ++ return false; ++ } ++ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down ++ // Paper end - Add EntityZapEvent + entitywitch1.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); + entitywitch1.setPersistenceRequired(); + this.releaseAllPois(); +- }); ++ return true; // Paper start - Add EntityZapEvent ++ }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit + + if (entitywitch == null) { + super.thunderHit(world, lightning); +@@ -855,6 +895,12 @@ + + @Override + protected void updateTrades() { ++ // Paper start - More vanilla friendly methods to update trades ++ updateTrades(TRADES_PER_LEVEL); ++ } ++ ++ public boolean updateTrades(int amount) { ++ // Paper end - More vanilla friendly methods to update trades + VillagerData villagerdata = this.getVillagerData(); + Int2ObjectMap int2objectmap; + +@@ -872,9 +918,11 @@ + if (avillagertrades_imerchantrecipeoption != null) { + MerchantOffers merchantrecipelist = this.getOffers(); + +- this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 2); ++ this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, amount); // Paper - More vanilla friendly methods to update trades ++ return true; // Paper - More vanilla friendly methods to update trades + } + } ++ return false; // Paper - More vanilla friendly methods to update trades + } + + public void gossip(ServerLevel world, Villager villager, long time) { +@@ -906,7 +954,7 @@ + }).limit(5L).toList(); + + if (list1.size() >= requiredCount) { +- if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false).isEmpty()) { ++ if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one + list.forEach(GolemSensor::golemDetected); + } + } +@@ -963,7 +1011,7 @@ + @Override + public void startSleeping(BlockPos pos) { + super.startSleeping(pos); +- this.brain.setMemory(MemoryModuleType.LAST_SLEPT, (Object) this.level().getGameTime()); ++ this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error + this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); + this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); + } +@@ -971,7 +1019,7 @@ + @Override + public void stopSleeping() { + super.stopSleeping(); +- this.brain.setMemory(MemoryModuleType.LAST_WOKEN, (Object) this.level().getGameTime()); ++ this.brain.setMemory(MemoryModuleType.LAST_WOKEN, this.level().getGameTime()); // CraftBukkit - decompile error + } + + private boolean golemSpawnConditionsMet(long worldTime) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch new file mode 100644 index 0000000000..a21a5e780e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/entity/npc/VillagerTrades.java ++++ b/net/minecraft/world/entity/npc/VillagerTrades.java +@@ -1834,7 +1834,8 @@ + return null; + } else { + ServerLevel serverLevel = (ServerLevel)entity.level(); +- BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, true); ++ if (!serverLevel.paperConfig().environment.treasureMaps.enabled) return null; // Paper - Configurable cartographer treasure maps ++ BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredVillager); // Paper - Configurable cartographer treasure maps + if (blockPos != null) { + ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), (byte)2, true, true); + MapItem.renderBiomePreviewMap(serverLevel, itemStack); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch new file mode 100644 index 0000000000..c3bde269c7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch @@ -0,0 +1,80 @@ +--- a/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -48,25 +48,38 @@ + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang3.tuple.Pair; + +-public class WanderingTrader extends AbstractVillager implements Consumable.OverrideConsumeSound { ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.VillagerAcquireTradeEvent; ++// CraftBukkit end + ++public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager implements Consumable.OverrideConsumeSound { ++ + private static final int NUMBER_OF_TRADE_OFFERS = 5; + @Nullable + private BlockPos wanderTarget; + private int despawnDelay; ++ // Paper start - Add more WanderingTrader API ++ public boolean canDrinkPotion = true; ++ public boolean canDrinkMilk = true; ++ // Paper end - Add more WanderingTrader API + + public WanderingTrader(EntityType type, Level world) { + super(type, world); ++ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { +- return this.level().isNight() && !entityvillagertrader.isInvisible(); ++ return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { +- return this.level().isDay() && entityvillagertrader.isInvisible(); ++ return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); +@@ -137,7 +150,16 @@ + MerchantOffer merchantrecipe = villagertrades_imerchantrecipeoption.getOffer(this, this.random); + + if (merchantrecipe != null) { +- merchantrecipelist.add(merchantrecipe); ++ // CraftBukkit start ++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (!event.isCancelled()) { ++ merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ } ++ // CraftBukkit end + } + + } +@@ -190,7 +212,7 @@ + if (offer.shouldRewardExp()) { + int i = 3 + this.random.nextInt(4); + +- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +@@ -244,7 +266,7 @@ + + private void maybeDespawn() { + if (this.despawnDelay > 0 && !this.isTrading() && --this.despawnDelay == 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch new file mode 100644 index 0000000000..5b4f2d10a7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch @@ -0,0 +1,100 @@ +--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -40,43 +40,53 @@ + + public WanderingTraderSpawner(ServerLevelData properties) { + this.serverLevelData = properties; +- this.tickDelay = 1200; +- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); +- this.spawnChance = properties.getWanderingTraderSpawnChance(); +- if (this.spawnDelay == 0 && this.spawnChance == 0) { +- this.spawnDelay = 24000; +- properties.setWanderingTraderSpawnDelay(this.spawnDelay); +- this.spawnChance = 25; +- properties.setWanderingTraderSpawnChance(this.spawnChance); +- } ++ // Paper start - Add Wandering Trader spawn rate config options ++ this.tickDelay = Integer.MIN_VALUE; ++ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //if (this.spawnDelay == 0 && this.spawnChance == 0) { ++ // this.spawnDelay = 24000; ++ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); ++ // this.spawnChance = 25; ++ // properties.setWanderingTraderSpawnChance(this.spawnChance); ++ //} ++ // Paper end - Add Wandering Trader spawn rate config options + + } + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ // Paper start - Add Wandering Trader spawn rate config options ++ if (this.tickDelay == Integer.MIN_VALUE) { ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ } + if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay > 0) { ++ } else if (this.tickDelay - 1 > 0) { ++ this.tickDelay = this.tickDelay - 1; + return 0; + } else { +- this.tickDelay = 1200; +- this.spawnDelay -= 1200; +- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.spawnDelay > 0) { + return 0; + } else { +- this.spawnDelay = 24000; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; + if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { + int i = this.spawnChance; + +- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); +- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways ++ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(world)) { +- this.spawnChance = 25; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ // Paper end - Add Wandering Trader spawn rate config options + return 1; + } else { + return 0; +@@ -110,7 +120,7 @@ + return false; + } + +- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, blockposition2, EntitySpawnReason.EVENT); ++ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called + + if (entityvillagertrader != null) { + for (int i = 0; i < 2; ++i) { +@@ -118,7 +128,7 @@ + } + + this.serverLevelData.setWanderingTraderId(entityvillagertrader.getUUID()); +- entityvillagertrader.setDespawnDelay(48000); ++ // entityvillagertrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent + entityvillagertrader.setWanderTarget(blockposition1); + entityvillagertrader.restrictTo(blockposition1, 16); + return true; +@@ -133,7 +143,7 @@ + BlockPos blockposition = this.findSpawnPositionNear(world, wanderingTrader.blockPosition(), range); + + if (blockposition != null) { +- TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT); ++ TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit + + if (entityllamatrader != null) { + entityllamatrader.setLeashedTo(wanderingTrader, true); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch new file mode 100644 index 0000000000..be3c5cccdb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch @@ -0,0 +1,130 @@ +--- a/net/minecraft/world/entity/player/Inventory.java ++++ b/net/minecraft/world/entity/player/Inventory.java +@@ -23,6 +23,12 @@ + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import java.util.ArrayList; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class Inventory implements Container, Nameable { + +@@ -38,7 +44,55 @@ + public int selected; + public final Player player; + 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(this.items.size() + this.armor.size() + this.offhand.size()); ++ for (List sub : this.compartments) { ++ combined.addAll(sub); ++ } ++ ++ return combined; ++ } ++ ++ public List getArmorContents() { ++ return this.armor; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return this.player.getBukkitEntity(); ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } + ++ @Override ++ public Location getLocation() { ++ return this.player.getBukkitEntity().getLocation(); ++ } ++ // CraftBukkit end ++ + public Inventory(Player player) { + this.items = NonNullList.withSize(36, ItemStack.EMPTY); + this.armor = NonNullList.withSize(4, ItemStack.EMPTY); +@@ -56,9 +110,31 @@ + } + + private boolean hasRemainingSpaceForItem(ItemStack existingStack, ItemStack stack) { +- return !existingStack.isEmpty() && ItemStack.isSameItemSameComponents(existingStack, stack) && existingStack.isStackable() && existingStack.getCount() < this.getMaxStackSize(existingStack); ++ return !existingStack.isEmpty() && existingStack.isStackable() && existingStack.getCount() < this.getMaxStackSize(existingStack) && ItemStack.isSameItemSameComponents(existingStack, stack); // Paper - check if itemstack is stackable first + } + ++ // CraftBukkit start - Watch method above! :D ++ public int canHold(ItemStack itemstack) { ++ int remains = itemstack.getCount(); ++ for (int i = 0; i < this.items.size(); ++i) { ++ ItemStack itemstack1 = this.getItem(i); ++ if (itemstack1.isEmpty()) return itemstack.getCount(); ++ ++ if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) { ++ remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount(); ++ } ++ if (remains <= 0) return itemstack.getCount(); ++ } ++ ItemStack offhandItemStack = this.getItem(this.items.size() + this.armor.size()); ++ if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) { ++ remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount(); ++ } ++ if (remains <= 0) return itemstack.getCount(); ++ ++ return itemstack.getCount() - remains; ++ } ++ // CraftBukkit end ++ + public int getFreeSlot() { + for (int i = 0; i < this.items.size(); ++i) { + if (((ItemStack) this.items.get(i)).isEmpty()) { +@@ -69,8 +145,10 @@ + return -1; + } + +- public void addAndPickItem(ItemStack stack) { +- this.selected = this.getSuitableHotbarSlot(); ++ // Paper start - Add PlayerPickItemEvent ++ public void addAndPickItem(ItemStack stack, final int targetSlot) { ++ this.selected = targetSlot; ++ // Paper end - Add PlayerPickItemEvent + if (!((ItemStack) this.items.get(this.selected)).isEmpty()) { + int i = this.getFreeSlot(); + +@@ -82,8 +160,10 @@ + this.items.set(this.selected, stack); + } + +- public void pickSlot(int slot) { +- this.selected = this.getSuitableHotbarSlot(); ++ // Paper start - Add PlayerPickItemEvent ++ public void pickSlot(int slot, final int targetSlot) { ++ this.selected = targetSlot; ++ // Paper end - Add PlayerPickItemEvent + ItemStack itemstack = (ItemStack) this.items.get(this.selected); + + this.items.set(this.selected, (ItemStack) this.items.get(slot)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch new file mode 100644 index 0000000000..3f8a17b363 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -0,0 +1,735 @@ +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -118,6 +118,15 @@ + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.Scoreboard; + import org.slf4j.Logger; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.util.CraftVector; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityDamageEvent; ++import org.bukkit.event.entity.EntityExhaustionEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerVelocityEvent; ++// CraftBukkit end + + public abstract class Player extends LivingEntity { + +@@ -139,7 +148,8 @@ + private static final int CURRENT_IMPULSE_CONTEXT_RESET_GRACE_TIME_TICKS = 40; + public static final Vec3 DEFAULT_VEHICLE_ATTACHMENT = new Vec3(0.0D, 0.6D, 0.0D); + public static final EntityDimensions STANDING_DIMENSIONS = EntityDimensions.scalable(0.6F, 1.8F).withEyeHeight(1.62F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT)); +- private static final Map POSES = ImmutableMap.builder().put(Pose.STANDING, Player.STANDING_DIMENSIONS).put(Pose.SLEEPING, Player.SLEEPING_DIMENSIONS).put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.CROUCHING, EntityDimensions.scalable(0.6F, 1.5F).withEyeHeight(1.27F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT))).put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F).withEyeHeight(1.62F)).build(); ++ // CraftBukkit - decompile error ++ private static final Map POSES = ImmutableMap.builder().put(Pose.STANDING, Player.STANDING_DIMENSIONS).put(Pose.SLEEPING, Player.SLEEPING_DIMENSIONS).put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.CROUCHING, EntityDimensions.scalable(0.6F, 1.5F).withEyeHeight(1.27F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT))).put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F).withEyeHeight(1.62F)).build(); + private static final EntityDataAccessor DATA_PLAYER_ABSORPTION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.FLOAT); + private static final EntityDataAccessor DATA_SCORE_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.INT); + public static final EntityDataAccessor DATA_PLAYER_MODE_CUSTOMISATION = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE); +@@ -149,7 +159,7 @@ + public static final int CLIENT_LOADED_TIMEOUT_TIME = 60; + private long timeEntitySatOnShoulder; + final Inventory inventory = new Inventory(this); +- protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(); ++ protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(this); // CraftBukkit - add "this" to constructor + public final InventoryMenu inventoryMenu; + public AbstractContainerMenu containerMenu; + protected FoodData foodData = new FoodData(); +@@ -181,13 +191,25 @@ + private Optional lastDeathLocation; + @Nullable + public FishingHook fishing; +- protected float hurtDir; ++ public float hurtDir; // Paper - protected -> public + @Nullable + public Vec3 currentImpulseImpactPos; + @Nullable + public Entity currentExplosionCause; + private boolean ignoreFallDamageFromCurrentImpulse; + private int currentImpulseContextResetGraceTime; ++ public boolean affectsSpawning = true; // Paper - Affects Spawning API ++ public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage ++ ++ // CraftBukkit start ++ public boolean fauxSleeping; ++ public int oldLevel = -1; ++ ++ @Override ++ public CraftHumanEntity getBukkitEntity() { ++ return (CraftHumanEntity) super.getBukkitEntity(); ++ } ++ // CraftBukkit end + + public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { + super(EntityType.PLAYER, world); +@@ -244,6 +266,13 @@ + + if (this.isSleeping()) { + ++this.sleepCounter; ++ // Paper start - Add PlayerDeepSleepEvent ++ if (this.sleepCounter == SLEEP_DURATION) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { ++ this.sleepCounter = Integer.MIN_VALUE; ++ } ++ } ++ // Paper end - Add PlayerDeepSleepEvent + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + } +@@ -261,7 +290,7 @@ + this.updateIsUnderwater(); + super.tick(); + if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -353,7 +382,7 @@ + } + + private void turtleHelmetTick() { +- this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true)); ++ this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit + } + + private boolean isEquipped(Item item) { +@@ -511,7 +540,19 @@ + super.handleEntityEvent(status); + } + ++ } ++ ++ // Paper start - Inventory close reason; unused code, but to keep signatures aligned ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ closeContainer(); ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - Inventory close reason ++ // Paper start - special close for unloaded inventory ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ this.containerMenu = this.inventoryMenu; + } ++ // Paper end - special close for unloaded inventory + + public void closeContainer() { + this.containerMenu = this.inventoryMenu; +@@ -523,8 +564,14 @@ + public void rideTick() { + if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) { + this.stopRiding(); +- this.setShiftKeyDown(false); +- } else { ++ // CraftBukkit start - SPIGOT-7316: no longer passenger, dismount and return ++ if (!this.isPassenger()) { ++ this.setShiftKeyDown(false); ++ return; ++ } ++ } ++ { ++ // CraftBukkit end + super.rideTick(); + this.oBob = this.bob; + this.bob = 0.0F; +@@ -593,6 +640,7 @@ + this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft()); + this.playShoulderEntityAmbientSound(this.getShoulderEntityRight()); + if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) { ++ if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay + this.removeEntitiesOnShoulder(); + } + +@@ -719,7 +767,14 @@ + + @Nullable + public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { +- if (!stack.isEmpty() && this.level().isClientSide) { ++ // CraftBukkit start - SPIGOT-2942: Add boolean to call event ++ return this.drop(stack, throwRandomly, retainOwnership, true); ++ } ++ ++ @Nullable ++ public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) { ++ // CraftBukkit end ++ if (!itemstack.isEmpty() && this.level().isClientSide) { + this.swing(InteractionHand.MAIN_HAND); + } + +@@ -809,7 +864,7 @@ + } + + if (nbt.contains("LastDeathLocation", 10)) { +- DataResult dataresult = GlobalPos.CODEC.parse(NbtOps.INSTANCE, nbt.get("LastDeathLocation")); ++ DataResult dataresult = GlobalPos.CODEC.parse(NbtOps.INSTANCE, nbt.get("LastDeathLocation")); // CraftBukkit - decompile error + Logger logger = Player.LOGGER; + + Objects.requireNonNull(logger); +@@ -817,7 +872,7 @@ + } + + if (nbt.contains("current_explosion_impact_pos", 9)) { +- DataResult dataresult1 = Vec3.CODEC.parse(NbtOps.INSTANCE, nbt.get("current_explosion_impact_pos")); ++ DataResult dataresult1 = Vec3.CODEC.parse(NbtOps.INSTANCE, nbt.get("current_explosion_impact_pos")); // CraftBukkit - decompile error + Logger logger1 = Player.LOGGER; + + Objects.requireNonNull(logger1); +@@ -854,7 +909,7 @@ + } + + this.getLastDeathLocation().flatMap((globalpos) -> { +- DataResult dataresult = GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, globalpos); ++ DataResult dataresult = GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, globalpos); // CraftBukkit - decompile error + Logger logger = Player.LOGGER; + + Objects.requireNonNull(logger); +@@ -886,10 +941,10 @@ + if (this.isDeadOrDying()) { + return false; + } else { +- this.removeEntitiesOnShoulder(); ++ // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down + if (source.scalesWithDifficulty()) { + if (world.getDifficulty() == Difficulty.PEACEFUL) { +- amount = 0.0F; ++ return false; // CraftBukkit - f = 0.0f -> return false + } + + if (world.getDifficulty() == Difficulty.EASY) { +@@ -901,7 +956,13 @@ + } + } + +- return amount == 0.0F ? false : super.hurtServer(world, source, amount); ++ // CraftBukkit start - Don't filter out 0 damage ++ boolean damaged = super.hurtServer(world, source, amount); ++ if (damaged) { ++ this.removeEntitiesOnShoulder(); ++ } ++ return damaged; ++ // CraftBukkit end + } + } + } +@@ -912,7 +973,7 @@ + ItemStack itemstack = this.getItemBlockingWith(); + + if (attacker.canDisableShield() && itemstack != null) { +- this.disableShield(itemstack); ++ this.disableShield(itemstack, attacker); // Paper - Add PlayerShieldDisableEvent + } + + } +@@ -923,10 +984,29 @@ + } + + public boolean canHarmPlayer(Player player) { +- PlayerTeam scoreboardteam = this.getTeam(); +- PlayerTeam scoreboardteam1 = player.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 (player instanceof ServerPlayer) { ++ ServerPlayer thatPlayer = (ServerPlayer) player; ++ 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 = player.level().getCraftServer().getOfflinePlayer(player.getScoreboardName()); ++ team = player.level().getCraftServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer); ++ if (team == null || team.allowFriendlyFire()) { ++ return true; ++ } ++ } + +- return scoreboardteam == null ? true : (!scoreboardteam.isAlliedTo(scoreboardteam1) ? true : scoreboardteam.isAllowFriendlyFire()); ++ if (this instanceof ServerPlayer) { ++ return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity()); ++ } ++ return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName())); ++ // CraftBukkit end + } + + @Override +@@ -966,32 +1046,38 @@ + } + } + ++ // CraftBukkit start + @Override +- protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) { +- if (!this.isInvulnerableTo(world, source)) { +- amount = this.getDamageAfterArmorAbsorb(source, amount); +- amount = this.getDamageAfterMagicAbsorb(source, amount); +- float f1 = amount; ++ protected boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { // void -> boolean ++ if (true) { ++ return super.actuallyHurt(worldserver, damagesource, f, event); ++ } ++ // CraftBukkit end ++ if (!this.isInvulnerableTo(worldserver, damagesource)) { ++ f = this.getDamageAfterArmorAbsorb(damagesource, f); ++ f = this.getDamageAfterMagicAbsorb(damagesource, f); ++ float f1 = f; + +- amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F); +- this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - amount)); +- float f2 = f1 - amount; ++ f = Math.max(f - this.getAbsorptionAmount(), 0.0F); ++ this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - f)); ++ float f2 = f1 - f; + + if (f2 > 0.0F && f2 < 3.4028235E37F) { + this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F)); + } + +- if (amount != 0.0F) { +- this.causeFoodExhaustion(source.getFoodExhaustion()); +- this.getCombatTracker().recordDamage(source, amount); +- this.setHealth(this.getHealth() - amount); +- if (amount < 3.4028235E37F) { +- this.awardStat(Stats.DAMAGE_TAKEN, Math.round(amount * 10.0F)); ++ if (f != 0.0F) { ++ this.causeFoodExhaustion(damagesource.getFoodExhaustion(), EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent ++ this.getCombatTracker().recordDamage(damagesource, f); ++ this.setHealth(this.getHealth() - f); ++ if (f < 3.4028235E37F) { ++ this.awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F)); + } + + this.gameEvent(GameEvent.ENTITY_DAMAGE); + } + } ++ return false; // CraftBukkit + } + + public boolean isTextFilteringEnabled() { +@@ -1061,13 +1147,19 @@ + + @Override + public void removeVehicle() { +- super.removeVehicle(); ++ // Paper start - Force entity dismount during teleportation ++ this.removeVehicle(false); ++ } ++ @Override ++ public void removeVehicle(boolean suppressCancellation) { ++ super.removeVehicle(suppressCancellation); ++ // Paper end - Force entity dismount during teleportation + this.boardingCooldown = 0; + } + + @Override + protected boolean isImmobile() { +- return super.isImmobile() || this.isSleeping(); ++ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override +@@ -1134,8 +1226,17 @@ + } + + public void attack(Entity target) { +- if (target.isAttackable()) { +- if (!target.skipAttackInteraction(this)) { ++ // Paper start - PlayerAttackEntityEvent ++ boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic ++ io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent( ++ (org.bukkit.entity.Player) this.getBukkitEntity(), ++ target.getBukkitEntity(), ++ willAttack ++ ); ++ ++ if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. ++ { ++ // Paper end - PlayerAttackEntityEvent + float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); + ItemStack itemstack = this.getWeaponItem(); + DamageSource damagesource = (DamageSource) Optional.ofNullable(itemstack.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this)); +@@ -1144,10 +1245,15 @@ + + f *= 0.2F + f2 * f2 * 0.8F; + f1 *= f2; +- this.resetAttackStrengthTicker(); ++ // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt + if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && target instanceof Projectile) { + Projectile iprojectile = (Projectile) target; + ++ // CraftBukkit start ++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(target, damagesource, f1, false)) { ++ return; ++ } ++ // CraftBukkit end + if (iprojectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) { + this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource()); + return; +@@ -1159,7 +1265,7 @@ + boolean flag1; + + if (this.isSprinting() && flag) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + flag1 = true; + } else { + flag1 = false; +@@ -1168,7 +1274,9 @@ + f += itemstack.getItem().getAttackDamageBonus(target, f, damagesource); + boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && !this.isSprinting(); + ++ flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + if (flag2) { ++ damagesource = damagesource.critical(true); // Paper start - critical damage API + f *= 1.5F; + } + +@@ -1202,13 +1310,17 @@ + if (target instanceof LivingEntity) { + LivingEntity entityliving1 = (LivingEntity) target; + +- entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); ++ entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - knockback events + } else { +- target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F)); ++ target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); ++ // Paper start - Configurable sprint interruption on attack ++ if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) { + this.setSprinting(false); ++ } ++ // Paper end - Configurable sprint interruption on attack + } + + LivingEntity entityliving2; +@@ -1223,8 +1335,13 @@ + if (entityliving2 != this && entityliving2 != target && !this.isAlliedTo((Entity) entityliving2) && (!(entityliving2 instanceof ArmorStand) || !((ArmorStand) entityliving2).isMarker()) && this.distanceToSqr((Entity) entityliving2) < 9.0D) { + float f7 = this.getEnchantedDamage(entityliving2, f6, damagesource) * f2; + +- entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); +- entityliving2.hurt(damagesource, f7); ++ // CraftBukkit start - Only apply knockback if the damage hits ++ if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep().critical(flag2), f7)) { // Paper - add critical damage API ++ continue; ++ } ++ // CraftBukkit end ++ entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // CraftBukkit // Paper - knockback events ++ // entityliving2.hurt(damagesource, f7); // CraftBukkit - moved up + Level world = this.level(); + + if (world instanceof ServerLevel) { +@@ -1235,26 +1352,43 @@ + } + } + +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.sweepAttack(); + } + + if (target instanceof ServerPlayer && target.hurtMarked) { ++ // CraftBukkit start - Add Velocity Event ++ boolean cancelled = false; ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity(); ++ org.bukkit.util.Vector velocity = CraftVector.toBukkit(vec3d); ++ ++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone()); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ cancelled = true; ++ } else if (!velocity.equals(event.getVelocity())) { ++ player.setVelocity(event.getVelocity()); ++ } ++ ++ if (!cancelled) { + ((ServerPlayer) target).connection.send(new ClientboundSetEntityMotionPacket(target)); + target.hurtMarked = false; + target.setDeltaMovement(vec3d); ++ } ++ // CraftBukkit end + } + + if (flag2) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.crit(target); + } + + if (!flag2 && !flag3) { + if (flag) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } else { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } + } + +@@ -1308,9 +1442,14 @@ + } + } + +- this.causeFoodExhaustion(0.1F); ++ this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility ++ // CraftBukkit start - resync on cancelled event ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer) this).getBukkitEntity().updateInventory(); ++ } ++ // CraftBukkit end + } + } + +@@ -1327,8 +1466,21 @@ + this.attack(target); + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent + public void disableShield(ItemStack shield) { +- this.getCooldowns().addCooldown(shield, 100); ++ // Paper start - Add PlayerShieldDisableEvent ++ this.disableShield(shield, null); ++ } ++ public void disableShield(ItemStack shield, @Nullable LivingEntity attacker) { ++ final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null; ++ if (finalAttacker != null) { ++ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100); ++ if (!shieldDisableEvent.callEvent()) return; ++ this.getCooldowns().addCooldown(shield, shieldDisableEvent.getCooldown()); ++ } else { ++ this.getCooldowns().addCooldown(shield, 100); ++ } ++ // Paper end - Add PlayerShieldDisableEvent + this.stopUsingItem(); + this.level().broadcastEntityEvent(this, (byte) 30); + } +@@ -1351,7 +1503,14 @@ + + @Override + public void remove(Entity.RemovalReason reason) { +- super.remove(reason); ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ super.remove(entity_removalreason, cause); ++ // CraftBukkit end + this.inventoryMenu.removed(this); + if (this.containerMenu != null && this.hasContainerOpen()) { + this.doCloseContainer(); +@@ -1391,7 +1550,13 @@ + } + + public Either startSleepInBed(BlockPos pos) { +- this.startSleeping(pos); ++ // CraftBukkit start ++ return this.startSleepInBed(pos, false); ++ } ++ ++ public Either startSleepInBed(BlockPos blockposition, boolean force) { ++ // CraftBukkit end ++ this.startSleeping(blockposition); + this.sleepCounter = 0; + return Either.right(Unit.INSTANCE); + } +@@ -1503,7 +1668,7 @@ + + @Override + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { +- if (this.abilities.mayfly) { ++ if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage + return false; + } else { + if (fallDistance >= 2.0F) { +@@ -1545,12 +1710,24 @@ + } + + public void startFallFlying() { +- this.setSharedFlag(7, true); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) { ++ this.setSharedFlag(7, true); ++ } else { ++ // SPIGOT-5542: must toggle like below ++ this.setSharedFlag(7, true); ++ this.setSharedFlag(7, false); ++ } ++ // CraftBukkit end + } + + public void stopFallFlying() { ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) { + this.setSharedFlag(7, true); + this.setSharedFlag(7, false); ++ } ++ // CraftBukkit end + } + + @Override +@@ -1662,13 +1839,32 @@ + } + + public int getXpNeededForNextLevel() { +- return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); ++ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints ++ } ++ // Paper start - send while respecting visibility ++ private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { ++ fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself ++ if (fromEntity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong())); ++ } + } ++ // Paper end - send while respecting visibility + ++ // CraftBukkit start + public void causeFoodExhaustion(float exhaustion) { ++ this.causeFoodExhaustion(exhaustion, EntityExhaustionEvent.ExhaustionReason.UNKNOWN); ++ } ++ ++ public void causeFoodExhaustion(float f, EntityExhaustionEvent.ExhaustionReason reason) { ++ // CraftBukkit end + if (!this.abilities.invulnerable) { + if (!this.level().isClientSide) { +- this.foodData.addExhaustion(exhaustion); ++ // CraftBukkit start ++ EntityExhaustionEvent event = CraftEventFactory.callPlayerExhaustionEvent(this, reason, f); ++ if (!event.isCancelled()) { ++ this.foodData.addExhaustion(event.getExhaustion()); ++ } ++ // CraftBukkit end + } + + } +@@ -1748,13 +1944,20 @@ + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +- this.verifyEquippedItem(stack); +- if (slot == EquipmentSlot.MAINHAND) { +- this.onEquipItem(slot, (ItemStack) this.inventory.items.set(this.inventory.selected, stack), stack); +- } else if (slot == EquipmentSlot.OFFHAND) { +- this.onEquipItem(slot, (ItemStack) this.inventory.offhand.set(0, stack), stack); +- } else if (slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { +- this.onEquipItem(slot, (ItemStack) this.inventory.armor.set(slot.getIndex(), stack), stack); ++ // CraftBukkit start ++ this.setItemSlot(slot, stack, false); ++ } ++ ++ @Override ++ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) { ++ // CraftBukkit end ++ this.verifyEquippedItem(itemstack); ++ if (enumitemslot == EquipmentSlot.MAINHAND) { ++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.items.set(this.inventory.selected, itemstack), itemstack, silent); // CraftBukkit ++ } else if (enumitemslot == EquipmentSlot.OFFHAND) { ++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.offhand.set(0, itemstack), itemstack, silent); // CraftBukkit ++ } else if (enumitemslot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { ++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.armor.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit + } + + } +@@ -1798,26 +2001,55 @@ + + public void removeEntitiesOnShoulder() { + if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) { +- this.respawnEntityOnShoulder(this.getShoulderEntityLeft()); ++ // CraftBukkit start ++ if (this.respawnEntityOnShoulder(this.getShoulderEntityLeft())) { ++ this.setShoulderEntityLeft(new CompoundTag()); ++ } ++ if (this.respawnEntityOnShoulder(this.getShoulderEntityRight())) { ++ this.setShoulderEntityRight(new CompoundTag()); ++ } ++ // CraftBukkit end ++ } ++ ++ } ++ ++ // Paper start - release entity api ++ public Entity releaseLeftShoulderEntity() { ++ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft()); ++ if (entity != null) { + this.setShoulderEntityLeft(new CompoundTag()); +- this.respawnEntityOnShoulder(this.getShoulderEntityRight()); ++ } ++ return entity; ++ } ++ ++ public Entity releaseRightShoulderEntity() { ++ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight()); ++ if (entity != null) { + this.setShoulderEntityRight(new CompoundTag()); + } ++ return entity; ++ } ++ // Paper end - release entity api + ++ private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean ++ // Paper start - release entity api - return entity - overload ++ return this.respawnEntityOnShoulder0(nbttagcompound) != null; + } + +- private void respawnEntityOnShoulder(CompoundTag entityNbt) { +- if (!this.level().isClientSide && !entityNbt.isEmpty()) { +- EntityType.create(entityNbt, this.level(), EntitySpawnReason.LOAD).ifPresent((entity) -> { ++ private Entity respawnEntityOnShoulder0(CompoundTag nbttagcompound) { // CraftBukkit void->boolean ++ // Paper end - release entity api - return entity - overload ++ if (!this.level().isClientSide && !nbttagcompound.isEmpty()) { ++ return EntityType.create(nbttagcompound, this.level(), EntitySpawnReason.LOAD).map((entity) -> { // CraftBukkit + if (entity instanceof TamableAnimal) { + ((TamableAnimal) entity).setOwnerUUID(this.uuid); + } + + entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ()); +- ((ServerLevel) this.level()).addWithUUID(entity); +- }); ++ return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY) ? entity : null; // CraftBukkit // Paper start - release entity api - return entity ++ }).orElse(null); // CraftBukkit // Paper end - release entity api - return entity + } + ++ return null; // Paper - return null + } + + @Override +@@ -2003,20 +2235,31 @@ + @Override + public ImmutableList getDismountPoses() { + return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); ++ } ++ ++ // Paper start - PlayerReadyArrowEvent ++ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { ++ return !(this instanceof ServerPlayer) || ++ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( ++ ((ServerPlayer) this).getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) ++ ).callEvent(); + } ++ // Paper end - PlayerReadyArrowEvent + + @Override + public ItemStack getProjectile(ItemStack stack) { + if (!(stack.getItem() instanceof ProjectileWeaponItem)) { + return ItemStack.EMPTY; + } else { +- Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles(); ++ Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate); + + if (!itemstack1.isEmpty()) { + return itemstack1; + } else { +- predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles(); ++ predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + + for (int i = 0; i < this.inventory.getContainerSize(); ++i) { + ItemStack itemstack2 = this.inventory.getItem(i); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch new file mode 100644 index 0000000000..a8a26d91a8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/world/entity/player/ProfilePublicKey.java ++++ b/net/minecraft/world/entity/player/ProfilePublicKey.java +@@ -24,7 +24,7 @@ + + public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException { + if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) { +- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE); ++ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes + } else { + return new ProfilePublicKey(publicKeyData); + } +@@ -88,8 +88,16 @@ + } + + public static class ValidationException extends ThrowingComponent { ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper + public ValidationException(Component messageText) { ++ // Paper start ++ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { ++ // Paper end + super(messageText); ++ this.kickCause = kickCause; // Paper + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch new file mode 100644 index 0000000000..d2cc2fa1a9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch @@ -0,0 +1,320 @@ +--- a/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -36,6 +36,7 @@ + import net.minecraft.world.entity.OminousItemSpawner; + import net.minecraft.world.entity.SlotAccess; + import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; +@@ -50,6 +51,10 @@ + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerPickupArrowEvent; ++// CraftBukkit end + + public abstract class AbstractArrow extends Projectile { + +@@ -78,6 +83,18 @@ + @Nullable + public ItemStack firedFromWeapon; + ++ // Spigot Start ++ @Override ++ public void inactiveTick() ++ { ++ if ( this.isInGround() ) ++ { ++ this.life += 1; ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ + protected AbstractArrow(EntityType type, Level world) { + super(type, world); + this.pickup = AbstractArrow.Pickup.DISALLOWED; +@@ -88,23 +105,30 @@ + } + + protected AbstractArrow(EntityType type, double x, double y, double z, Level world, ItemStack stack, @Nullable ItemStack weapon) { +- this(type, world); +- this.pickupItemStack = stack.copy(); +- this.setCustomName((Component) stack.get(DataComponents.CUSTOM_NAME)); +- Unit unit = (Unit) stack.remove(DataComponents.INTANGIBLE_PROJECTILE); ++ // CraftBukkit start - handle the owner before the rest of things ++ this(type, x, y, z, world, stack, weapon, null); ++ } + ++ protected AbstractArrow(EntityType entitytypes, double d0, double d1, double d2, Level world, ItemStack itemstack, @Nullable ItemStack itemstack1, @Nullable LivingEntity ownerEntity) { ++ this(entitytypes, world); ++ this.setOwner(ownerEntity); ++ // CraftBukkit end ++ this.pickupItemStack = itemstack.copy(); ++ this.setCustomName((Component) itemstack.get(DataComponents.CUSTOM_NAME)); ++ Unit unit = (Unit) itemstack.remove(DataComponents.INTANGIBLE_PROJECTILE); ++ + if (unit != null) { + this.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; + } + +- this.setPos(x, y, z); +- if (weapon != null && world instanceof ServerLevel worldserver) { +- if (weapon.isEmpty()) { ++ this.setPos(d0, d1, d2); ++ if (itemstack1 != null && world instanceof ServerLevel worldserver) { ++ if (itemstack1.isEmpty()) { + throw new IllegalArgumentException("Invalid weapon firing an arrow"); + } + +- this.firedFromWeapon = weapon.copy(); +- int i = EnchantmentHelper.getPiercingCount(worldserver, weapon, this.pickupItemStack); ++ this.firedFromWeapon = itemstack1.copy(); ++ int i = EnchantmentHelper.getPiercingCount(worldserver, itemstack1, this.pickupItemStack); + + if (i > 0) { + this.setPierceLevel((byte) i); +@@ -114,8 +138,8 @@ + } + + protected AbstractArrow(EntityType type, LivingEntity owner, Level world, ItemStack stack, @Nullable ItemStack shotFrom) { +- this(type, owner.getX(), owner.getEyeY() - 0.10000000149011612D, owner.getZ(), world, stack, shotFrom); +- this.setOwner(owner); ++ this(type, owner.getX(), owner.getEyeY() - 0.10000000149011612D, owner.getZ(), world, stack, shotFrom, owner); // CraftBukkit ++ // this.setOwner(entityliving); // SPIGOT-7744 - Moved to the above constructor + } + + public void setSoundEvent(SoundEvent sound) { +@@ -220,6 +244,7 @@ + } + + } else { ++ if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds + this.inGroundTime = 0; + Vec3 vec3d2 = this.position(); + +@@ -282,7 +307,7 @@ + + if (movingobjectpositionentity == null) { + if (this.isAlive() && blockHitResult.getType() != HitResult.Type.MISS) { +- this.hitTargetOrDeflectSelf(blockHitResult); ++ this.preHitTargetOrDeflectSelf(blockHitResult); // CraftBukkit - projectile hit event + this.hasImpulse = true; + } + } else { +@@ -290,7 +315,7 @@ + continue; + } + +- ProjectileDeflection projectiledeflection = this.hitTargetOrDeflectSelf(movingobjectpositionentity); ++ ProjectileDeflection projectiledeflection = this.preHitTargetOrDeflectSelf(movingobjectpositionentity); // CraftBukkit - projectile hit event + + this.hasImpulse = true; + if (this.getPierceLevel() > 0 && projectiledeflection == ProjectileDeflection.NONE) { +@@ -318,7 +343,20 @@ + this.level().addParticle(ParticleTypes.BUBBLE, pos.x - vec3d1.x * 0.25D, pos.y - vec3d1.y * 0.25D, pos.z - vec3d1.z * 0.25D, vec3d1.x, vec3d1.y, vec3d1.z); + } + ++ } ++ ++ // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows ++ @Override ++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) { ++ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) { ++ if (this.piercingIgnoreEntityIds == null) { ++ this.piercingIgnoreEntityIds = new IntOpenHashSet(5); ++ } ++ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId()); ++ } ++ return super.preHitTargetOrDeflectSelf(hitResult); + } ++ // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows + + @Override + protected double getDefaultGravity() { +@@ -356,8 +394,8 @@ + + protected void tickDespawn() { + ++this.life; +- if (this.life >= 1200) { +- this.discard(); ++ if (this.life >= (pickup == Pickup.CREATIVE_ONLY ? this.level().paperConfig().entities.spawning.creativeArrowDespawnRate.value() : (pickup == Pickup.DISALLOWED ? this.level().paperConfig().entities.spawning.nonPlayerArrowDespawnRate.value() : ((this instanceof ThrownTrident) ? this.level().spigotConfig.tridentDespawnRate : this.level().spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - Configurable non-player arrow despawn rate; TODO: Extract this to init? ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -386,9 +424,9 @@ + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param + if (!this.isInGround()) { +- super.push(deltaX, deltaY, deltaZ); ++ super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param + } + } + +@@ -423,7 +461,7 @@ + } + + if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + return; + } + +@@ -440,11 +478,18 @@ + entityliving.setLastHurtMob(entity); + } + ++ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API + boolean flag = entity.getType() == EntityType.ENDERMAN; + int k = entity.getRemainingFireTicks(); + + if (this.isOnFire() && !flag) { +- entity.igniteForSeconds(5.0F); ++ // CraftBukkit start ++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5.0F); ++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent); ++ if (!combustEvent.isCancelled()) { ++ entity.igniteForSeconds(combustEvent.getDuration(), false); ++ } ++ // CraftBukkit end + } + + if (entity.hurtOrSimulate(damagesource, (float) i)) { +@@ -490,7 +535,7 @@ + + this.playSound(this.soundEvent, 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F)); + if (this.getPierceLevel() <= 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + } else { + entity.setRemainingFireTicks(k); +@@ -506,7 +551,7 @@ + this.spawnAtLocation(worldserver2, this.getPickupItem(), 0.1F); + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + } + } +@@ -538,7 +583,7 @@ + Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale(d0 * 0.6D * d1); + + if (vec3d.lengthSqr() > 0.0D) { +- target.push(vec3d.x, 0.1D, vec3d.z); ++ target.push(vec3d.x, 0.1D, vec3d.z, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + } + +@@ -665,7 +710,7 @@ + this.setCritArrow(nbt.getBoolean("crit")); + this.setPierceLevel(nbt.getByte("PierceLevel")); + if (nbt.contains("SoundEvent", 8)) { +- this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.parse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); ++ this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location + } + + if (nbt.contains("item", 10)) { +@@ -675,7 +720,7 @@ + } + + if (nbt.contains("weapon", 10)) { +- this.firedFromWeapon = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("weapon")).orElse((Object) null); ++ this.firedFromWeapon = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("weapon")).orElse(null); // CraftBukkit - decompile error + } else { + this.firedFromWeapon = null; + } +@@ -684,38 +729,42 @@ + + @Override + public void setOwner(@Nullable Entity entity) { ++ // Paper start - Fix PickupStatus getting reset ++ this.setOwner(entity, true); ++ } ++ ++ public void setOwner(@Nullable Entity entity, boolean resetPickup) { ++ // Paper end - Fix PickupStatus getting reset + super.setOwner(entity); ++ if (!resetPickup) return; // Paper - Fix PickupStatus getting reset + Entity entity1 = entity; + byte b0 = 0; + +- EntityArrow.PickupStatus entityarrow_pickupstatus; ++ EntityArrow.PickupStatus entityarrow_pickupstatus = this.pickup; // CraftBukkit - decompile error + + label16: +- while(true) { +- //$FF: b0->value +- //0->net/minecraft/world/entity/player/EntityHuman +- //1->net/minecraft/world/entity/OminousItemSpawner +- switch (entity1.typeSwitch(entity1, b0)) { +- case -1: +- default: +- entityarrow_pickupstatus = this.pickup; +- break label16; +- case 0: +- EntityHuman entityhuman = (EntityHuman)entity1; ++ // CraftBukkit start - decompile error ++ while (true) { ++ switch (entity1) { ++ case EntityHuman entityhuman: + + if (this.pickup != EntityArrow.PickupStatus.DISALLOWED) { + b0 = 1; +- break; ++ break label16; + } + + entityarrow_pickupstatus = EntityArrow.PickupStatus.ALLOWED; + break label16; +- case 1: +- OminousItemSpawner ominousitemspawner = (OminousItemSpawner)entity1; ++ case OminousItemSpawner ominousitemspawner: + + entityarrow_pickupstatus = EntityArrow.PickupStatus.DISALLOWED; + break label16; ++ case null: // SPIGOT-7751: Fix crash caused by null owner ++ default: ++ entityarrow_pickupstatus = this.pickup; ++ break label16; + } ++ // CraftBukkit end + } + + this.pickup = entityarrow_pickupstatus; +@@ -724,9 +773,24 @@ + @Override + public void playerTouch(Player player) { + if (!this.level().isClientSide && (this.isInGround() || this.isNoPhysics()) && this.shakeTime <= 0) { +- if (this.tryPickup(player)) { ++ // CraftBukkit start ++ ItemStack itemstack = this.getPickupItem(); ++ if (this.pickup == Pickup.ALLOWED && !itemstack.isEmpty() && player.getInventory().canHold(itemstack) > 0) { ++ ItemEntity item = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack); ++ PlayerPickupArrowEvent event = new PlayerPickupArrowEvent((org.bukkit.entity.Player) player.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.level().getCraftServer(), item), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity()); ++ // event.setCancelled(!entityhuman.canPickUpLoot); TODO ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ itemstack = item.getItem(); ++ } ++ ++ if ((this.pickup == AbstractArrow.Pickup.ALLOWED && player.getInventory().add(itemstack)) || (this.pickup == AbstractArrow.Pickup.CREATIVE_ONLY && player.getAbilities().instabuild)) { ++ // CraftBukkit end + player.take(this, 1); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch new file mode 100644 index 0000000000..b53dd9f069 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java ++++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +@@ -14,12 +14,17 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public abstract class AbstractHurtingProjectile extends Projectile { + + public static final double INITAL_ACCELERATION_POWER = 0.1D; + public static final double DEFLECTION_SCALE = 0.5D; + public double accelerationPower; ++ public float bukkitYield = 1; // CraftBukkit ++ public boolean isIncendiary = true; // CraftBukkit + + protected AbstractHurtingProjectile(EntityType type, Level world) { + super(type, world); +@@ -69,7 +74,7 @@ + + this.applyInertia(); + if (!this.level().isClientSide && (entity != null && entity.isRemoved() || !this.level().hasChunkAt(this.blockPosition()))) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType()); + Vec3 vec3d; +@@ -89,7 +94,7 @@ + } + + if (movingobjectposition.getType() != HitResult.Type.MISS && this.isAlive()) { +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + } + + this.createParticleTrail(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch new file mode 100644 index 0000000000..c19bed3b4d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/entity/projectile/Arrow.java ++++ b/net/minecraft/world/entity/projectile/Arrow.java +@@ -119,7 +119,7 @@ + mobeffect = (MobEffectInstance) iterator.next(); + target.addEffect(new MobEffectInstance(mobeffect.getEffect(), Math.max(mobeffect.mapDuration((i) -> { + return i / 8; +- }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity); ++ }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit + } + } + +@@ -127,7 +127,7 @@ + + while (iterator.hasNext()) { + mobeffect = (MobEffectInstance) iterator.next(); +- target.addEffect(mobeffect, entity); ++ target.addEffect(mobeffect, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch new file mode 100644 index 0000000000..6278d9b3bb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/entity/projectile/DragonFireball.java ++++ b/net/minecraft/world/entity/projectile/DragonFireball.java +@@ -14,6 +14,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class DragonFireball extends AbstractHurtingProjectile { + +@@ -59,9 +62,11 @@ + } + } + ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); +- this.level().addFreshEntity(entityareaeffectcloud); +- this.discard(); ++ this.level().addFreshEntity(entityareaeffectcloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason ++ } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch new file mode 100644 index 0000000000..fc93ad07be --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/entity/projectile/EvokerFangs.java ++++ b/net/minecraft/world/entity/projectile/EvokerFangs.java +@@ -16,6 +16,9 @@ + import net.minecraft.world.entity.TraceableEntity; + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class EvokerFangs extends Entity implements TraceableEntity { + +@@ -121,7 +124,7 @@ + } + + if (--this.lifeTicks < 0) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + } + +@@ -132,7 +135,7 @@ + + if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) { + if (entityliving1 == null) { +- target.hurt(this.damageSources().magic(), 6.0F); ++ target.hurt(this.damageSources().magic().customEventDamager(this), 6.0F); // CraftBukkit // Paper - fix DamageSource API + } else { + if (entityliving1.isAlliedTo((Entity) target)) { + return; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch new file mode 100644 index 0000000000..67aa21fe00 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/world/entity/projectile/EyeOfEnder.java ++++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java +@@ -17,6 +17,9 @@ + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class EyeOfEnder extends Entity implements ItemSupplier { + +@@ -73,6 +76,11 @@ + } + + public void signalTo(BlockPos pos) { ++ // Paper start - Change EnderEye target without changing other things ++ this.signalTo(pos, true); ++ } ++ public void signalTo(BlockPos pos, boolean update) { ++ // Paper end - Change EnderEye target without changing other things + double d0 = (double) pos.getX(); + int i = pos.getY(); + double d1 = (double) pos.getZ(); +@@ -90,8 +98,10 @@ + this.tz = d1; + } + ++ if (update) { // Paper - Change EnderEye target without changing other things + this.life = 0; + this.surviveAfterDeath = this.random.nextInt(5) > 0; ++ } // Paper - Change EnderEye target without changing other things + } + + @Override +@@ -153,7 +163,7 @@ + ++this.life; + if (this.life > 80 && !this.level().isClientSide) { + this.playSound(SoundEvents.ENDER_EYE_DEATH, 1.0F, 1.0F); +- this.discard(); ++ this.discard(this.surviveAfterDeath ? EntityRemoveEvent.Cause.DROP : EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + if (this.surviveAfterDeath) { + this.level().addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), this.getItem())); + } else { +@@ -174,7 +184,12 @@ + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + if (nbt.contains("Item", 10)) { +- this.setItem((ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem())); ++ // CraftBukkit start - SPIGOT-6103 summon, see also SPIGOT-5474 ++ ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem()); ++ if (!itemstack.isEmpty()) { ++ this.setItem(itemstack); ++ } ++ // CraftBukkit end + } else { + this.setItem(this.getDefaultItem()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch new file mode 100644 index 0000000000..6cc358f744 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/entity/projectile/Fireball.java ++++ b/net/minecraft/world/entity/projectile/Fireball.java +@@ -61,7 +61,12 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + if (nbt.contains("Item", 10)) { +- this.setItem((ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem())); ++ // CraftBukkit start - SPIGOT-5474 probably came from bugged earlier versions ++ ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem()); ++ if (!itemstack.isEmpty()) { ++ this.setItem(itemstack); ++ } ++ // CraftBukkit end + } else { + this.setItem(this.getDefaultItem()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch new file mode 100644 index 0000000000..91715aeb75 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch @@ -0,0 +1,132 @@ +--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java ++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +@@ -32,6 +32,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class FireworkRocketEntity extends Projectile implements ItemSupplier { + +@@ -42,6 +45,7 @@ + public int lifetime; + @Nullable + public LivingEntity attachedToEntity; ++ @Nullable public java.util.UUID spawningEntity; // Paper + + public FireworkRocketEntity(EntityType type, Level world) { + super(type, world); +@@ -84,7 +88,29 @@ + this.setOwner(entity); + } + ++ // Spigot Start - copied from tick + @Override ++ public void inactiveTick() { ++ this.life += 1; ++ ++ if (this.life > this.lifetime) { ++ Level world = this.level(); ++ ++ if (world instanceof ServerLevel) { ++ ServerLevel worldserver = (ServerLevel) world; ++ ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { ++ this.explode(worldserver); ++ } ++ // CraftBukkit end ++ } ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ ++ @Override + protected void defineSynchedData(SynchedEntityData.Builder builder) { + builder.define(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, FireworkRocketEntity.getDefaultItem()); + builder.define(FireworkRocketEntity.DATA_ATTACHED_TO_TARGET, OptionalInt.empty()); +@@ -152,7 +178,7 @@ + } + + if (!this.noPhysics && this.isAlive() && movingobjectposition.getType() != HitResult.Type.MISS) { +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + this.hasImpulse = true; + } + +@@ -172,7 +198,11 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.explode(worldserver); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { ++ this.explode(worldserver); ++ } ++ // CraftBukkit end + } + } + +@@ -182,7 +212,7 @@ + world.broadcastEntityEvent(this, (byte) 17); + this.gameEvent(GameEvent.EXPLODE, this.getOwner()); + this.dealExplosionDamage(world); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause + } + + @Override +@@ -191,7 +221,11 @@ + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +- this.explode(worldserver); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { ++ this.explode(worldserver); ++ } ++ // CraftBukkit end + } + + } +@@ -205,7 +239,11 @@ + + if (world instanceof ServerLevel worldserver) { + if (this.hasExplosion()) { +- this.explode(worldserver); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { ++ this.explode(worldserver); ++ } ++ // CraftBukkit end + } + } + +@@ -287,6 +325,11 @@ + nbt.putInt("LifeTime", this.lifetime); + nbt.put("FireworksItem", this.getItem().save(this.registryAccess())); + nbt.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE)); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbt.putUUID("SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -303,7 +346,11 @@ + if (nbt.contains("ShotAtAngle")) { + this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle")); + } +- ++ // Paper start ++ if (nbt.hasUUID("SpawningEntity")) { ++ this.spawningEntity = nbt.getUUID("SpawningEntity"); ++ } ++ // Paper end + } + + private List getExplosions() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch new file mode 100644 index 0000000000..27f0995455 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch @@ -0,0 +1,352 @@ +--- a/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/net/minecraft/world/entity/projectile/FishingHook.java +@@ -29,7 +29,6 @@ + import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.MoverType; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; +@@ -47,6 +46,13 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.entity.Player; ++import org.bukkit.entity.FishHook; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerFishEvent; ++// CraftBukkit end ++ + public class FishingHook extends Projectile { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -68,6 +74,18 @@ + private final int luck; + private final int lureSpeed; + ++ // CraftBukkit start - Extra variables to enable modification of fishing wait time, values are minecraft defaults ++ public int minWaitTime = 100; ++ public int maxWaitTime = 600; ++ public int minLureTime = 20; ++ public int maxLureTime = 80; ++ public float minLureAngle = 0.0F; ++ public float maxLureAngle = 360.0F; ++ public boolean applyLure = true; ++ public boolean rainInfluenced = true; ++ public boolean skyInfluenced = true; ++ // CraftBukkit end ++ + private FishingHook(EntityType type, Level world, int luckBonus, int waitTimeReductionTicks) { + super(type, world); + this.syncronizedRandom = RandomSource.create(); +@@ -75,13 +93,17 @@ + this.currentState = FishingHook.FishHookState.FLYING; + this.luck = Math.max(0, luckBonus); + this.lureSpeed = Math.max(0, waitTimeReductionTicks); ++ // Paper start - Configurable fishing time ranges ++ minWaitTime = world.paperConfig().fishingTimeRange.minimum; ++ maxWaitTime = world.paperConfig().fishingTimeRange.maximum; ++ // Paper end - Configurable fishing time ranges + } + + public FishingHook(EntityType type, Level world) { + this(type, world, 0, 0); + } + +- public FishingHook(Player thrower, Level world, int luckBonus, int waitTimeReductionTicks) { ++ public FishingHook(net.minecraft.world.entity.player.Player thrower, Level world, int luckBonus, int waitTimeReductionTicks) { + this(EntityType.FISHING_BOBBER, world, luckBonus, waitTimeReductionTicks); + this.setOwner(thrower); + float f = thrower.getXRot(); +@@ -149,15 +171,15 @@ + public void tick() { + this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level().getGameTime()); + super.tick(); +- Player entityhuman = this.getPlayerOwner(); ++ net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); + + if (entityhuman == null) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (this.level().isClientSide || !this.shouldStopFishing(entityhuman)) { + if (this.onGround()) { + ++this.life; + if (this.life >= 1200) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; + } + } else { +@@ -250,7 +272,7 @@ + } + } + +- private boolean shouldStopFishing(Player player) { ++ private boolean shouldStopFishing(net.minecraft.world.entity.player.Player player) { + ItemStack itemstack = player.getMainHandItem(); + ItemStack itemstack1 = player.getOffhandItem(); + boolean flag = itemstack.is(Items.FISHING_ROD); +@@ -259,7 +281,7 @@ + if (!player.isRemoved() && player.isAlive() && (flag || flag1) && this.distanceToSqr((Entity) player) <= 1024.0D) { + return false; + } else { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return true; + } + } +@@ -267,7 +289,7 @@ + private void checkCollision() { + HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + } + + @Override +@@ -300,11 +322,11 @@ + int i = 1; + BlockPos blockposition1 = pos.above(); + +- if (this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) { ++ if (this.rainInfluenced && this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) { // CraftBukkit + ++i; + } + +- if (this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) { ++ if (this.skyInfluenced && this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) { // CraftBukkit + --i; + } + +@@ -314,6 +336,10 @@ + this.timeUntilLured = 0; + this.timeUntilHooked = 0; + this.getEntityData().set(FishingHook.DATA_BITING, false); ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT); ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ // CraftBukkit end + } + } else { + float f; +@@ -347,6 +373,13 @@ + worldserver.sendParticles(ParticleTypes.FISHING, d0, d1, d2, 0, (double) (-f4), 0.01D, (double) f3, 1.0D); + } + } else { ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.BITE); ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ if (playerFishEvent.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F); + double d3 = this.getY() + 0.5D; + +@@ -379,16 +412,34 @@ + } + + if (this.timeUntilLured <= 0) { +- this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F); +- this.timeUntilHooked = Mth.nextInt(this.random, 20, 80); ++ // CraftBukkit start - logic to modify fishing wait time, lure time, and lure angle ++ this.fishAngle = Mth.nextFloat(this.random, this.minLureAngle, this.maxLureAngle); ++ this.timeUntilHooked = Mth.nextInt(this.random, this.minLureTime, this.maxLureTime); ++ // CraftBukkit end ++ // Paper start - Add missing fishing event state ++ if (this.getPlayerOwner() != null) { ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.LURED); ++ if (!playerFishEvent.callEvent()) { ++ this.timeUntilHooked = 0; ++ return; ++ } ++ } ++ // Paper end - Add missing fishing event state + } + } else { +- this.timeUntilLured = Mth.nextInt(this.random, 100, 600); +- this.timeUntilLured -= this.lureSpeed; ++ // CraftBukkit start - logic to modify fishing wait time ++ this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic ++ // CraftBukkit end + } + } + + } ++ // Paper start - more projectile api - extract time until lured reset logic ++ public void resetTimeUntilLured() { ++ this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); ++ this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop ++ } ++ // Paper end - more projectile api - extract time until lured reset logic + + public boolean calculateOpenWater(BlockPos pos) { + FishingHook.OpenWaterType entityfishinghook_waterposition = FishingHook.OpenWaterType.INVALID; +@@ -445,17 +496,35 @@ + @Override + public void readAdditionalSaveData(CompoundTag nbt) {} + ++ // Paper start - Add hand parameter to PlayerFishEvent ++ @Deprecated ++ @io.papermc.paper.annotation.DoNotUse + public int retrieve(ItemStack usedItem) { +- Player entityhuman = this.getPlayerOwner(); ++ return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem); ++ } + ++ public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) { ++ // Paper end - Add hand parameter to PlayerFishEvent ++ net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); ++ + if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) { + int i = 0; + + if (this.hookedIn != null) { ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ if (this.hookedIn != null) { // Paper - re-check to see if there is a hooked entity ++ // CraftBukkit end + this.pullEntity(this.hookedIn); + CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer) entityhuman, usedItem, this, Collections.emptyList()); + this.level().broadcastEntityEvent(this, (byte) 31); + i = this.hookedIn instanceof ItemEntity ? 3 : 5; ++ } // Paper - re-check to see if there is a hooked entity + } else if (this.nibble > 0) { + LootParams lootparams = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.TOOL, usedItem).withParameter(LootContextParams.THIS_ENTITY, this).withLuck((float) this.luck + entityhuman.getLuck()).create(LootContextParamSets.FISHING); + LootTable loottable = this.level().getServer().reloadableRegistries().getLootTable(BuiltInLootTables.FISHING); +@@ -466,15 +535,38 @@ + + while (iterator.hasNext()) { + ItemStack itemstack1 = (ItemStack) iterator.next(); +- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); ++ // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty ++ // if the item stack is empty we instead just have our entityitem as null ++ ItemEntity entityitem = null; ++ if (!itemstack1.isEmpty()) { ++ entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); ++ } ++ // Paper end ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent ++ playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ // CraftBukkit end + double d0 = entityhuman.getX() - this.getX(); + double d1 = entityhuman.getY() - this.getY(); + double d2 = entityhuman.getZ() - this.getZ(); + double d3 = 0.1D; + +- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); +- this.level().addFreshEntity(entityitem); +- entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, this.random.nextInt(6) + 1)); ++ // Paper start - entity item can be null, so we need to check against this ++ if (entityitem != null) { ++ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); ++ this.level().addFreshEntity(entityitem); ++ } ++ // Paper end ++ // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() ++ if (playerFishEvent.getExpToDrop() > 0) { ++ entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper ++ } ++ // CraftBukkit end + if (itemstack1.is(ItemTags.FISHES)) { + entityhuman.awardStat(Stats.FISH_CAUGHT, 1); + } +@@ -484,10 +576,27 @@ + } + + if (this.onGround()) { ++ // CraftBukkit start ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ // CraftBukkit end + i = 2; + } ++ // CraftBukkit start ++ if (i == 0) { ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent ++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ if (playerFishEvent.isCancelled()) { ++ return 0; ++ } ++ } ++ // CraftBukkit end + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return i; + } else { + return 0; +@@ -496,7 +605,7 @@ + + @Override + public void handleEntityEvent(byte status) { +- if (status == 31 && this.level().isClientSide && this.hookedIn instanceof Player && ((Player) this.hookedIn).isLocalPlayer()) { ++ if (status == 31 && this.level().isClientSide && this.hookedIn instanceof net.minecraft.world.entity.player.Player && ((net.minecraft.world.entity.player.Player) this.hookedIn).isLocalPlayer()) { + this.pullEntity(this.hookedIn); + } + +@@ -520,8 +629,15 @@ + + @Override + public void remove(Entity.RemovalReason reason) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end + this.updateOwnerInfo((FishingHook) null); +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause + } + + @Override +@@ -536,7 +652,7 @@ + } + + private void updateOwnerInfo(@Nullable FishingHook fishingBobber) { +- Player entityhuman = this.getPlayerOwner(); ++ net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); + + if (entityhuman != null) { + entityhuman.fishing = fishingBobber; +@@ -545,10 +661,10 @@ + } + + @Nullable +- public Player getPlayerOwner() { ++ public net.minecraft.world.entity.player.Player getPlayerOwner() { + Entity entity = this.getOwner(); + +- return entity instanceof Player ? (Player) entity : null; ++ return entity instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) entity : null; + } + + @Nullable +@@ -575,7 +691,7 @@ + int i = packet.getData(); + + FishingHook.LOGGER.error("Failed to recreate fishing hook on client. {} (id: {}) is not a valid owner.", this.level().getEntity(i), i); +- this.discard(); ++ this.discard(null); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch new file mode 100644 index 0000000000..494d25292d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch @@ -0,0 +1,56 @@ +--- a/net/minecraft/world/entity/projectile/LargeFireball.java ++++ b/net/minecraft/world/entity/projectile/LargeFireball.java +@@ -12,6 +12,10 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end + + public class LargeFireball extends Fireball { + +@@ -19,11 +23,13 @@ + + public LargeFireball(EntityType type, Level world) { + super(type, world); ++ this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + } + + public LargeFireball(Level world, LivingEntity owner, Vec3 velocity, int explosionPower) { + super(EntityType.FIREBALL, owner, velocity, world); + this.explosionPower = explosionPower; ++ this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + } + + @Override +@@ -34,8 +40,16 @@ + if (world instanceof ServerLevel worldserver) { + boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + +- this.level().explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionPower, flag, Level.ExplosionInteraction.MOB); +- this.discard(); ++ // CraftBukkit start - fire ExplosionPrimeEvent ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ // give 'this' instead of (Entity) null so we know what causes the damage ++ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -65,7 +79,8 @@ + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + if (nbt.contains("ExplosionPower", 99)) { +- this.explosionPower = nbt.getByte("ExplosionPower"); ++ // CraftBukkit - set bukkitYield when setting explosionpower ++ this.bukkitYield = this.explosionPower = nbt.getByte("ExplosionPower"); + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch new file mode 100644 index 0000000000..370c83f5da --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/entity/projectile/LlamaSpit.java ++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java +@@ -17,6 +17,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class LlamaSpit extends Projectile { + +@@ -41,7 +44,7 @@ + Vec3 vec3d = this.getDeltaMovement(); + HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + double d0 = this.getX() + vec3d.x; + double d1 = this.getY() + vec3d.y; + double d2 = this.getZ() + vec3d.z; +@@ -50,9 +53,9 @@ + float f = 0.99F; + + if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (this.isInWaterOrBubble()) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { + this.setDeltaMovement(vec3d.scale(0.9900000095367432D)); + this.applyGravity(); +@@ -83,7 +86,7 @@ + protected void onHitBlock(BlockHitResult blockHitResult) { + super.onHitBlock(blockHitResult); + if (!this.level().isClientSide) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch new file mode 100644 index 0000000000..4e0aeff12d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch @@ -0,0 +1,231 @@ +--- a/net/minecraft/world/entity/projectile/Projectile.java ++++ b/net/minecraft/world/entity/projectile/Projectile.java +@@ -35,6 +35,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.projectiles.ProjectileSource; ++// CraftBukkit end + + public abstract class Projectile extends Entity implements TraceableEntity { + +@@ -47,6 +50,10 @@ + @Nullable + private Entity lastDeflectedBy; + ++ // CraftBukkit start ++ protected boolean hitCancelled = false; ++ // CraftBukkit end ++ + Projectile(EntityType type, Level world) { + super(type, world); + } +@@ -56,16 +63,35 @@ + this.ownerUUID = entity.getUUID(); + this.cachedOwner = entity; + } +- ++ // Paper start - Refresh ProjectileSource for projectiles ++ else { ++ this.ownerUUID = null; ++ this.cachedOwner = null; ++ this.projectileSource = null; ++ } ++ // Paper end - Refresh ProjectileSource for projectiles ++ this.refreshProjectileSource(false); // Paper + } ++ // Paper start - Refresh ProjectileSource for projectiles ++ public void refreshProjectileSource(boolean fillCache) { ++ if (fillCache) { ++ this.getOwner(); ++ } ++ if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) { ++ this.projectileSource = projSource; ++ } ++ } ++ // Paper end - Refresh ProjectileSource for projectiles + + @Nullable + @Override + public Entity getOwner() { + if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } else if (this.ownerUUID != null) { + this.cachedOwner = this.findOwner(this.ownerUUID); ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } else { + return null; +@@ -108,6 +134,7 @@ + protected void readAdditionalSaveData(CompoundTag nbt) { + if (nbt.hasUUID("Owner")) { + this.setOwnerThroughUUID(nbt.getUUID("Owner")); ++ if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && this.level().paperConfig().misc.legacyEnderPearlBehavior) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit + } + + this.leftOwner = nbt.getBoolean("LeftOwner"); +@@ -184,12 +211,20 @@ + + this.shoot((double) f5, (double) f6, (double) f7, speed, divergence); + Vec3 vec3d = shooter.getKnownMovement(); +- ++ // Paper start - allow disabling relative velocity ++ if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) { + this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, shooter.onGround() ? 0.0D : vec3d.y, vec3d.z)); ++ } ++ // Paper end - allow disabling relative velocity + } + + public static T spawnProjectileFromRotation(Projectile.ProjectileFactory creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) { +- return Projectile.spawnProjectile(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> { ++ // Paper start - PlayerLaunchProjectileEvent ++ return spawnProjectileFromRotationDelayed(creator, world, projectileStack, shooter, roll, power, divergence).spawn(); ++ } ++ public static Delayed spawnProjectileFromRotationDelayed(Projectile.ProjectileFactory creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) { ++ return Projectile.spawnProjectileDelayed(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> { ++ // Paper end - PlayerLaunchProjectileEvent + iprojectile.shootFromRotation(shooter, shooter.getXRot(), shooter.getYRot(), roll, power, divergence); + }); + } +@@ -201,7 +236,12 @@ + } + + public static T spawnProjectileUsingShoot(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) { +- return Projectile.spawnProjectile(projectile, world, projectileStack, (iprojectile) -> { ++ // Paper start - fixes and addition to spawn reason API ++ return spawnProjectileUsingShootDelayed(projectile, world, projectileStack, velocityX, velocityY, velocityZ, power, divergence).spawn(); ++ } ++ public static Delayed spawnProjectileUsingShootDelayed(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) { ++ return Projectile.spawnProjectileDelayed(projectile, world, projectileStack, (iprojectile) -> { ++ // Paper end - fixes and addition to spawn reason API + projectile.shoot(velocityX, velocityY, velocityZ, power, divergence); + }); + } +@@ -211,11 +251,45 @@ + }); + } + ++ // Paper start - delayed projectile spawning ++ public record Delayed( ++ T projectile, ++ ServerLevel world, ++ ItemStack projectileStack ++ ) { ++ // Taken from net.minecraft.world.entity.projectile.Projectile.spawnProjectile(T, net.minecraft.server.level.ServerLevel, net.minecraft.world.item.ItemStack, java.util.function.Consumer) ++ public boolean attemptSpawn() { ++ if (!world.addFreshEntity(projectile)) return false; ++ projectile.applyOnProjectileSpawned(this.world, this.projectileStack); ++ return true; ++ } ++ ++ public T spawn() { ++ this.attemptSpawn(); ++ return projectile(); ++ } ++ ++ public boolean attemptSpawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ if (!world.addFreshEntity(projectile, reason)) return false; ++ projectile.applyOnProjectileSpawned(this.world, this.projectileStack); ++ return true; ++ } ++ ++ public T spawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ this.attemptSpawn(reason); ++ return projectile(); ++ } ++ } ++ // Paper end - delayed projectile spawning ++ + public static T spawnProjectile(T projectile, ServerLevel world, ItemStack projectileStack, Consumer beforeSpawn) { ++ // Paper start - delayed projectile spawning ++ return spawnProjectileDelayed(projectile, world, projectileStack, beforeSpawn).spawn(); ++ } ++ public static Delayed spawnProjectileDelayed(T projectile, ServerLevel world, ItemStack projectileStack, Consumer beforeSpawn) { ++ // Paper end - delayed projectile spawning + beforeSpawn.accept(projectile); +- world.addFreshEntity(projectile); +- projectile.applyOnProjectileSpawned(world, projectileStack); +- return projectile; ++ return new Delayed<>(projectile, world, projectileStack); // Paper - delayed projectile spawning + } + + public void applyOnProjectileSpawned(ServerLevel world, ItemStack projectileStack) { +@@ -232,6 +306,17 @@ + + } + ++ // CraftBukkit start - call projectile hit event ++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public ++ org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); ++ this.hitCancelled = event != null && event.isCancelled(); ++ if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) { ++ return this.hitTargetOrDeflectSelf(movingobjectposition); ++ } ++ return ProjectileDeflection.NONE; ++ } ++ // CraftBukkit end ++ + protected ProjectileDeflection hitTargetOrDeflectSelf(HitResult hitResult) { + if (hitResult.getType() == HitResult.Type.ENTITY) { + EntityHitResult movingobjectpositionentity = (EntityHitResult) hitResult; +@@ -269,7 +354,13 @@ + public boolean deflect(ProjectileDeflection deflection, @Nullable Entity deflector, @Nullable Entity owner, boolean fromAttack) { + deflection.deflect(this, deflector, this.random); + if (!this.level().isClientSide) { +- this.setOwner(owner); ++ // Paper start - Fix PickupStatus getting reset ++ if (this instanceof AbstractArrow arrow) { ++ arrow.setOwner(owner, false); ++ } else { ++ this.setOwner(owner); ++ } ++ // Paper end - Fix PickupStatus getting reset + this.onDeflection(deflector, fromAttack); + } + +@@ -309,6 +400,11 @@ + protected void onHitEntity(EntityHitResult entityHitResult) {} + + protected void onHitBlock(BlockHitResult blockHitResult) { ++ // CraftBukkit start - cancellable hit event ++ if (this.hitCancelled) { ++ return; ++ } ++ // CraftBukkit end + BlockState iblockdata = this.level().getBlockState(blockHitResult.getBlockPos()); + + iblockdata.onProjectileHit(this.level(), iblockdata, blockHitResult, this); +@@ -320,6 +416,15 @@ + } else { + Entity entity1 = this.getOwner(); + ++ // Paper start - Cancel hit for vanished players ++ if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) { ++ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); ++ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); ++ if (!shooter.canSee(collided)) { ++ return false; ++ } ++ } ++ // Paper end - Cancel hit for vanished players + return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); + } + } +@@ -333,14 +438,8 @@ + } + + protected static float lerpRotation(float prevRot, float newRot) { +- while (newRot - prevRot < -180.0F) { +- prevRot -= 360.0F; +- } ++ prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server + +- while (newRot - prevRot >= 180.0F) { +- prevRot += 360.0F; +- } +- + return Mth.lerp(0.2F, prevRot, newRot); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch new file mode 100644 index 0000000000..bac5f4b417 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch @@ -0,0 +1,100 @@ +--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java ++++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java +@@ -31,6 +31,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class ShulkerBullet extends Projectile { + +@@ -60,8 +63,21 @@ + this.finalTarget = target; + this.currentMoveDirection = Direction.UP; + this.selectNextMoveDirection(axis); ++ this.projectileSource = (org.bukkit.entity.LivingEntity) owner.getBukkitEntity(); // CraftBukkit + } + ++ // CraftBukkit start ++ public Entity getTarget() { ++ return this.finalTarget; ++ } ++ ++ public void setTarget(Entity e) { ++ this.finalTarget = e; ++ this.currentMoveDirection = Direction.UP; ++ this.selectNextMoveDirection(Direction.Axis.X); ++ } ++ // CraftBukkit end ++ + @Override + public SoundSource getSoundSource() { + return SoundSource.HOSTILE; +@@ -194,7 +210,7 @@ + @Override + public void checkDespawn() { + if (this.level().getDifficulty() == Difficulty.PEACEFUL) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -239,7 +255,7 @@ + } + + if (movingobjectposition != null && this.isAlive() && movingobjectposition.getType() != HitResult.Type.MISS) { +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + } + + ProjectileUtil.rotateTowardsMovement(this, 0.5F); +@@ -312,7 +328,7 @@ + if (entity instanceof LivingEntity) { + LivingEntity entityliving1 = (LivingEntity) entity; + +- entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this)); ++ entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + +@@ -326,14 +342,20 @@ + } + + private void destroy() { +- this.discard(); ++ // CraftBukkit start - add Bukkit remove cause ++ this.destroy(null); ++ } ++ ++ private void destroy(EntityRemoveEvent.Cause cause) { ++ this.discard(cause); ++ // CraftBukkit end + this.level().gameEvent((Holder) GameEvent.ENTITY_DAMAGE, this.position(), GameEvent.Context.of((Entity) this)); + } + + @Override + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); +- this.destroy(); ++ this.destroy(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + @Override +@@ -348,9 +370,14 @@ + + @Override + public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) { ++ return false; ++ } ++ // CraftBukkit end + this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F); + world.sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2D, 0.2D, 0.2D, 0.0D); +- this.destroy(); ++ this.destroy(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + return true; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch new file mode 100644 index 0000000000..65522ba256 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch @@ -0,0 +1,63 @@ +--- a/net/minecraft/world/entity/projectile/SmallFireball.java ++++ b/net/minecraft/world/entity/projectile/SmallFireball.java +@@ -15,6 +15,10 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class SmallFireball extends Fireball { + +@@ -24,6 +28,11 @@ + + public SmallFireball(Level world, LivingEntity owner, Vec3 velocity) { + super(EntityType.SMALL_FIREBALL, owner, velocity, world); ++ // CraftBukkit start ++ if (this.getOwner() != null && this.getOwner() instanceof Mob) { ++ this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); ++ } ++ // CraftBukkit end + } + + public SmallFireball(Level world, double x, double y, double z, Vec3 velocity) { +@@ -40,7 +49,14 @@ + Entity entity1 = this.getOwner(); + int i = entity.getRemainingFireTicks(); + +- entity.igniteForSeconds(5.0F); ++ // CraftBukkit start - Entity damage by entity event + combust event ++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent((org.bukkit.entity.Projectile) this.getBukkitEntity(), entity.getBukkitEntity(), 5.0F); ++ entity.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ entity.igniteForSeconds(event.getDuration(), false); ++ } ++ // CraftBukkit end + DamageSource damagesource = this.damageSources().fireball(this, entity1); + + if (!entity.hurtServer(worldserver, damagesource, 5.0F)) { +@@ -60,10 +76,10 @@ + if (world instanceof ServerLevel worldserver) { + Entity entity = this.getOwner(); + +- if (!(entity instanceof Mob) || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (this.isIncendiary) { // CraftBukkit + BlockPos blockposition = blockHitResult.getBlockPos().relative(blockHitResult.getDirection()); + +- if (this.level().isEmptyBlock(blockposition)) { ++ if (this.level().isEmptyBlock(blockposition) && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { // CraftBukkit + this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition)); + } + } +@@ -75,7 +91,7 @@ + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); + if (!this.level().isClientSide) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch new file mode 100644 index 0000000000..8d2ce7e8d8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/projectile/Snowball.java ++++ b/net/minecraft/world/entity/projectile/Snowball.java +@@ -13,6 +13,9 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class Snowball extends ThrowableItemProjectile { + +@@ -65,7 +68,7 @@ + super.onHit(hitResult); + if (!this.level().isClientSide) { + this.level().broadcastEntityEvent(this, (byte) 3); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch new file mode 100644 index 0000000000..629d60da70 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/projectile/SpectralArrow.java ++++ b/net/minecraft/world/entity/projectile/SpectralArrow.java +@@ -41,7 +41,7 @@ + super.doPostHurtEffects(target); + MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0); + +- target.addEffect(mobeffect, this.getEffectSource()); ++ target.addEffect(mobeffect, this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch new file mode 100644 index 0000000000..6471dbc99e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java ++++ b/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java +@@ -34,6 +34,12 @@ + + protected abstract Item getDefaultItem(); + ++ // CraftBukkit start ++ public Item getDefaultItemPublic() { ++ return this.getDefaultItem(); ++ } ++ // CraftBukkit end ++ + @Override + public ItemStack getItem() { + return (ItemStack) this.getEntityData().get(ThrowableItemProjectile.DATA_ITEM_STACK); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch new file mode 100644 index 0000000000..db8a43fd04 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java ++++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java +@@ -63,7 +63,7 @@ + this.applyEffectsFromBlocks(); + super.tick(); + if (movingobjectposition.getType() != HitResult.Type.MISS && this.isAlive()) { +- this.hitTargetOrDeflectSelf(movingobjectposition); ++ this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch new file mode 100644 index 0000000000..5df84b160a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch @@ -0,0 +1,121 @@ +--- a/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -1,33 +1,39 @@ + package net.minecraft.world.entity.projectile; + +-import net.minecraft.core.particles.ItemParticleOption; +-import net.minecraft.core.particles.ParticleTypes; +-import net.minecraft.world.entity.EntityDimensions; + import net.minecraft.world.entity.EntitySpawnReason; +-import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.LivingEntity; +-import net.minecraft.world.entity.animal.Chicken; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++import net.minecraft.core.particles.ItemParticleOption; ++import net.minecraft.core.particles.ParticleTypes; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityDimensions; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerEggThrowEvent; ++// CraftBukkit end + + public class ThrownEgg extends ThrowableItemProjectile { + + private static final EntityDimensions ZERO_SIZED_DIMENSIONS = EntityDimensions.fixed(0.0F, 0.0F); + +- public ThrownEgg(EntityType type, Level world) { ++ public ThrownEgg(net.minecraft.world.entity.EntityType type, Level world) { + super(type, world); + } + + public ThrownEgg(Level world, LivingEntity owner, ItemStack stack) { +- super(EntityType.EGG, owner, world, stack); ++ super(net.minecraft.world.entity.EntityType.EGG, owner, world, stack); + } + + public ThrownEgg(Level world, double x, double y, double z, ItemStack stack) { +- super(EntityType.EGG, x, y, z, world, stack); ++ super(net.minecraft.world.entity.EntityType.EGG, x, y, z, world, stack); + } + + @Override +@@ -52,30 +58,65 @@ + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); + if (!this.level().isClientSide) { +- if (this.random.nextInt(8) == 0) { ++ // CraftBukkit start ++ boolean hatching = this.random.nextInt(8) == 0; ++ if (true) { ++ // CraftBukkit end + byte b0 = 1; + + if (this.random.nextInt(32) == 0) { + b0 = 4; + } + ++ // CraftBukkit start ++ EntityType hatchingType = EntityType.CHICKEN; ++ ++ Entity shooter = this.getOwner(); ++ if (!hatching) { ++ b0 = 0; ++ } ++ if (shooter instanceof ServerPlayer) { ++ PlayerEggThrowEvent event = new PlayerEggThrowEvent((Player) shooter.getBukkitEntity(), (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, b0, hatchingType); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ b0 = event.getNumHatches(); ++ hatching = event.isHatching(); ++ hatchingType = event.getHatchingType(); ++ // If hatching is set to false, ensure child count is 0 ++ if (!hatching) { ++ b0 = 0; ++ } ++ } ++ // CraftBukkit end ++ // Paper start - Add ThrownEggHatchEvent ++ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType); ++ event.callEvent(); ++ hatching = event.isHatching(); ++ b0 = hatching ? event.getNumHatches() : 0; // If hatching is set to false, ensure child count is 0 ++ hatchingType = event.getHatchingType(); ++ // Paper end - Add ThrownEggHatchEvent ++ + for (int i = 0; i < b0; ++i) { +- Chicken entitychicken = (Chicken) EntityType.CHICKEN.create(this.level(), EntitySpawnReason.TRIGGERED); ++ Entity entitychicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit + + if (entitychicken != null) { +- entitychicken.setAge(-24000); ++ // CraftBukkit start ++ if (entitychicken.getBukkitEntity() instanceof Ageable) { ++ ((Ageable) entitychicken.getBukkitEntity()).setBaby(); ++ } ++ // CraftBukkit end + entitychicken.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); + if (!entitychicken.fudgePositionAfterSizeChange(ThrownEgg.ZERO_SIZED_DIMENSIONS)) { + break; + } + +- this.level().addFreshEntity(entitychicken); ++ this.level().addFreshEntity(entitychicken, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit + } + } + } + + this.level().broadcastEntityEvent(this, (byte) 3); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch new file mode 100644 index 0000000000..f24cfeb088 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch @@ -0,0 +1,95 @@ +--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -24,10 +24,15 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++// CraftBukkit end + + public class ThrownEnderpearl extends ThrowableItemProjectile { + +@@ -140,12 +145,19 @@ + ServerPlayer entityplayer = (ServerPlayer) entity; + + if (entityplayer.connection.isAcceptingMessages()) { ++ // CraftBukkit start ++ ServerPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, PlayerTeleportEvent.TeleportCause.ENDER_PEARL)); ++ if (entityplayer1 == null) { ++ this.discard(EntityRemoveEvent.Cause.HIT); ++ return; ++ } ++ // CraftBukkit end + if (this.random.nextFloat() < 0.05F && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(worldserver, EntitySpawnReason.TRIGGERED); + + if (entityendermite != null) { + entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); +- worldserver.addFreshEntity(entityendermite); ++ worldserver.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); + } + } + +@@ -153,12 +165,12 @@ + entity.setPortalCooldown(); + } + +- ServerPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING)); ++ // EntityPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3D.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING)); // CraftBukkit - moved up + + if (entityplayer1 != null) { + entityplayer1.resetFallDistance(); + entityplayer1.resetCurrentImpulseContext(); +- entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl(), 5.0F); ++ entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API + } + + this.playSound(worldserver, vec3d); +@@ -173,11 +185,11 @@ + this.playSound(worldserver, vec3d); + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + return; + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + return; + } + } +@@ -210,7 +222,7 @@ + entity = this.getOwner(); + if (entity instanceof ServerPlayer entityplayer) { + if (!entity.isAlive() && entityplayer.serverLevel().getGameRules().getBoolean(GameRules.RULE_ENDER_PEARLS_VANISH_ON_DEATH)) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + break label30; + } + } +@@ -240,7 +252,7 @@ + Entity entity = super.teleport(teleportTarget); + + if (entity != null) { +- entity.placePortalTicket(BlockPos.containing(entity.position())); ++ if (!this.level().paperConfig().misc.legacyEnderPearlBehavior) entity.placePortalTicket(BlockPos.containing(entity.position())); // Paper - Allow using old ender pearl behavior + } + + return entity; +@@ -248,7 +260,7 @@ + + @Override + public boolean canTeleport(Level from, Level to) { +- if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { ++ if (from.getTypeKey() == LevelStem.END && to.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit + Entity entity = this.getOwner(); + + if (entity instanceof ServerPlayer) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch new file mode 100644 index 0000000000..40cd201036 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java ++++ b/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +@@ -9,6 +9,9 @@ + import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.HitResult; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class ThrownExperienceBottle extends ThrowableItemProjectile { + +@@ -38,11 +41,20 @@ + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); + if (this.level() instanceof ServerLevel) { +- this.level().levelEvent(2002, this.blockPosition(), -13083194); ++ // CraftBukkit - moved to after event ++ // this.level().levelEvent(2002, this.blockPosition(), -13083194); + int i = 3 + this.level().random.nextInt(5) + this.level().random.nextInt(5); + +- ExperienceOrb.award((ServerLevel) this.level(), this.position(), i); +- this.discard(); ++ // CraftBukkit start ++ org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, hitResult, i); ++ i = event.getExperience(); ++ if (event.getShowEffect()) { ++ this.level().levelEvent(2002, this.blockPosition(), -13083194); ++ } ++ // CraftBukkit end ++ ++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch new file mode 100644 index 0000000000..babee054f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch @@ -0,0 +1,322 @@ +--- a/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -10,6 +10,7 @@ + import net.minecraft.core.Holder; + import net.minecraft.core.component.DataComponents; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.tags.BlockTags; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.effect.MobEffect; +@@ -17,7 +18,6 @@ + import net.minecraft.world.entity.AreaEffectCloud; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntityType; +-import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.animal.axolotl.Axolotl; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.Item; +@@ -28,18 +28,28 @@ + import net.minecraft.world.item.alchemy.Potions; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.AbstractCandleBlock; ++// CraftBukkit start ++import java.util.HashMap; ++import java.util.Map; ++import net.minecraft.world.effect.MobEffects; ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.CampfireBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class ThrownPotion extends ThrowableItemProjectile { + + public static final double SPLASH_RANGE = 4.0D; + private static final double SPLASH_RANGE_SQ = 16.0D; +- public static final Predicate WATER_SENSITIVE_OR_ON_FIRE = (entityliving) -> { ++ public static final Predicate WATER_SENSITIVE_OR_ON_FIRE = (entityliving) -> { + return entityliving.isSensitiveToWater() || entityliving.isOnFire(); + }; + +@@ -47,7 +57,7 @@ + super(type, world); + } + +- public ThrownPotion(Level world, LivingEntity owner, ItemStack stack) { ++ public ThrownPotion(Level world, net.minecraft.world.entity.LivingEntity owner, ItemStack stack) { + super(EntityType.POTION, owner, world, stack); + } + +@@ -93,70 +103,96 @@ + @Override + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); ++ // Paper start - More projectile API ++ this.splash(hitResult); ++ } ++ public void splash(@Nullable HitResult hitResult) { ++ // Paper end - More projectile API + Level world = this.level(); +- + if (world instanceof ServerLevel worldserver) { + ItemStack itemstack = this.getItem(); + PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); + ++ boolean showParticles = true; // Paper - Fix potions splash events + if (potioncontents.is(Potions.WATER)) { +- this.applyWater(worldserver); +- } else if (potioncontents.hasEffects()) { ++ showParticles = this.applyWater(worldserver, hitResult); // Paper - Fix potions splash events ++ } else if (true || potioncontents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply + if (this.isLingering()) { +- this.makeAreaOfEffectCloud(potioncontents); ++ showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } else { +- this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null); ++ showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API + } + } + ++ if (showParticles) { // Paper - Fix potions splash events + int i = potioncontents.potion().isPresent() && ((Potion) ((Holder) potioncontents.potion().get()).value()).hasInstantEffects() ? 2007 : 2002; + + worldserver.levelEvent(i, this.blockPosition(), potioncontents.getColor()); +- this.discard(); ++ } // Paper - Fix potions splash events ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + } + +- private void applyWater(ServerLevel world) { ++ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events ++ private boolean applyWater(ServerLevel world, @Nullable HitResult hitResult) { // Paper - Fix potions splash events + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); +- List list = this.level().getEntitiesOfClass(LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE); ++ // Paper start - Fix potions splash events ++ List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); ++ Map affected = new HashMap<>(); ++ java.util.Set rehydrate = new java.util.HashSet<>(); ++ java.util.Set extinguish = new java.util.HashSet<>(); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +- LivingEntity entityliving = (LivingEntity) iterator.next(); ++ net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); ++ if (entityliving instanceof Axolotl axolotl) { ++ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity())); ++ } + double d0 = this.distanceToSqr((Entity) entityliving); + + if (d0 < 16.0D) { + if (entityliving.isSensitiveToWater()) { +- entityliving.hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ affected.put(entityliving.getBukkitLivingEntity(), 1.0); + } + + if (entityliving.isOnFire() && entityliving.isAlive()) { +- entityliving.extinguishFire(); ++ extinguish.add(entityliving.getBukkitLivingEntity()); + } + } + } + +- List list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb); +- Iterator iterator1 = list1.iterator(); +- +- while (iterator1.hasNext()) { +- Axolotl axolotl = (Axolotl) iterator1.next(); +- +- axolotl.rehydrate(); ++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent( ++ this, hitResult, affected, rehydrate, extinguish ++ ); ++ if (!event.isCancelled()) { ++ for (LivingEntity affectedEntity : event.getToDamage()) { ++ ((CraftLivingEntity) affectedEntity).getHandle().hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ } ++ for (LivingEntity toExtinguish : event.getToExtinguish()) { ++ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire(); ++ } ++ for (LivingEntity toRehydrate : event.getToRehydrate()) { ++ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) { ++ axolotl.rehydrate(); ++ } ++ } ++ // Paper end - Fix potions splash events + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void applySplash(ServerLevel world, Iterable effects, @Nullable Entity entity) { ++ private boolean applySplash(ServerLevel worldserver, Iterable iterable, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); +- List list = world.getEntitiesOfClass(LivingEntity.class, axisalignedbb); ++ List list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); ++ Map affected = new HashMap(); // CraftBukkit + + if (!list.isEmpty()) { + Entity entity1 = this.getEffectSource(); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +- LivingEntity entityliving = (LivingEntity) iterator.next(); ++ net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); + + if (entityliving.isAffectedByPotions()) { + double d0 = this.distanceToSqr((Entity) entityliving); +@@ -164,43 +200,71 @@ + if (d0 < 16.0D) { + double d1; + ++ // Paper - diff on change, used when calling the splash event for water splash potions + if (entityliving == entity) { + d1 = 1.0D; + } else { + d1 = 1.0D - Math.sqrt(d0) / 4.0D; + } + +- Iterator iterator1 = effects.iterator(); ++ // CraftBukkit start ++ affected.put((LivingEntity) entityliving.getBukkitEntity(), d1); ++ } ++ } ++ } ++ } + +- while (iterator1.hasNext()) { +- MobEffectInstance mobeffect = (MobEffectInstance) iterator1.next(); +- Holder holder = mobeffect.getEffect(); ++ org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, position, affected); ++ if (!event.isCancelled() && list != null && !list.isEmpty()) { // do not process effects if there are no effects to process ++ Entity entity1 = this.getEffectSource(); ++ for (LivingEntity victim : event.getAffectedEntities()) { ++ if (!(victim instanceof CraftLivingEntity)) { ++ continue; ++ } + +- if (((MobEffect) holder.value()).isInstantenous()) { +- ((MobEffect) holder.value()).applyInstantenousEffect(world, this, this.getOwner(), entityliving, mobeffect.getAmplifier(), d1); +- } else { +- int i = mobeffect.mapDuration((j) -> { +- return (int) (d1 * (double) j + 0.5D); +- }); +- MobEffectInstance mobeffect1 = new MobEffectInstance(holder, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()); ++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) victim).getHandle(); ++ double d1 = event.getIntensity(victim); ++ // CraftBukkit end + +- if (!mobeffect1.endsWithin(20)) { +- entityliving.addEffect(mobeffect1, entity1); +- } +- } ++ Iterator iterator1 = iterable.iterator(); ++ ++ while (iterator1.hasNext()) { ++ MobEffectInstance mobeffect = (MobEffectInstance) iterator1.next(); ++ Holder holder = mobeffect.getEffect(); ++ // CraftBukkit start - Abide by PVP settings - for players only! ++ if (!this.level().pvpMode && this.getOwner() instanceof ServerPlayer && entityliving instanceof ServerPlayer && entityliving != this.getOwner()) { ++ MobEffect mobeffectlist = (MobEffect) holder.value(); ++ if (mobeffectlist == MobEffects.MOVEMENT_SLOWDOWN || mobeffectlist == MobEffects.DIG_SLOWDOWN || mobeffectlist == MobEffects.HARM || mobeffectlist == MobEffects.BLINDNESS ++ || mobeffectlist == MobEffects.HUNGER || mobeffectlist == MobEffects.WEAKNESS || mobeffectlist == MobEffects.POISON) { ++ continue; + } + } ++ // CraftBukkit end ++ ++ if (((MobEffect) holder.value()).isInstantenous()) { ++ ((MobEffect) holder.value()).applyInstantenousEffect(worldserver, this, this.getOwner(), entityliving, mobeffect.getAmplifier(), d1); ++ } else { ++ int i = mobeffect.mapDuration((j) -> { ++ return (int) (d1 * (double) j + 0.5D); ++ }); ++ MobEffectInstance mobeffect1 = new MobEffectInstance(holder, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()); ++ ++ if (!mobeffect1.endsWithin(20)) { ++ entityliving.addEffect(mobeffect1, entity1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_SPLASH); // CraftBukkit ++ } ++ } + } + } + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void makeAreaOfEffectCloud(PotionContents potion) { ++ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + Entity entity = this.getOwner(); + +- if (entity instanceof LivingEntity entityliving) { ++ if (entity instanceof net.minecraft.world.entity.LivingEntity entityliving) { + entityareaeffectcloud.setOwner(entityliving); + } + +@@ -208,8 +272,17 @@ + entityareaeffectcloud.setRadiusOnUse(-0.5F); + entityareaeffectcloud.setWaitTime(10); + entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration()); +- entityareaeffectcloud.setPotionContents(potion); +- this.level().addFreshEntity(entityareaeffectcloud); ++ entityareaeffectcloud.setPotionContents(potioncontents); ++ boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events ++ // CraftBukkit start ++ org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); ++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && !entityareaeffectcloud.potionContents.hasEffects())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling ++ this.level().addFreshEntity(entityareaeffectcloud); ++ } else { ++ entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause ++ } ++ // CraftBukkit end ++ return !event.isCancelled(); // Paper - Fix potions splash events + } + + public boolean isLingering() { +@@ -220,19 +293,31 @@ + BlockState iblockdata = this.level().getBlockState(pos); + + if (iblockdata.is(BlockTags.FIRE)) { +- this.level().destroyBlock(pos, false, this); ++ // CraftBukkit start ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ this.level().destroyBlock(pos, false, this); ++ } ++ // CraftBukkit end + } else if (AbstractCandleBlock.isLit(iblockdata)) { +- AbstractCandleBlock.extinguish((Player) null, iblockdata, this.level(), pos); ++ // CraftBukkit start ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(AbstractCandleBlock.LIT, false))) { ++ AbstractCandleBlock.extinguish((Player) null, iblockdata, this.level(), pos); ++ } ++ // CraftBukkit end + } else if (CampfireBlock.isLitCampfire(iblockdata)) { +- this.level().levelEvent((Player) null, 1009, pos, 0); +- CampfireBlock.dowse(this.getOwner(), this.level(), pos, iblockdata); +- this.level().setBlockAndUpdate(pos, (BlockState) iblockdata.setValue(CampfireBlock.LIT, false)); ++ // CraftBukkit start ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(CampfireBlock.LIT, false))) { ++ this.level().levelEvent((Player) null, 1009, pos, 0); ++ CampfireBlock.dowse(this.getOwner(), this.level(), pos, iblockdata); ++ this.level().setBlockAndUpdate(pos, (BlockState) iblockdata.setValue(CampfireBlock.LIT, false)); ++ } ++ // CraftBukkit end + } + + } + + @Override +- public DoubleDoubleImmutablePair calculateHorizontalHurtKnockbackDirection(LivingEntity target, DamageSource source) { ++ public DoubleDoubleImmutablePair calculateHorizontalHurtKnockbackDirection(net.minecraft.world.entity.LivingEntity target, DamageSource source) { + double d0 = target.position().x - this.position().x; + double d1 = target.position().z - this.position().z; + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch new file mode 100644 index 0000000000..fff7300b9e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch @@ -0,0 +1,86 @@ +--- a/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -23,6 +23,9 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class ThrownTrident extends AbstractArrow { + +@@ -34,16 +37,19 @@ + + public ThrownTrident(EntityType type, Level world) { + super(type, world); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + } + + public ThrownTrident(Level world, LivingEntity owner, ItemStack stack) { + super(EntityType.TRIDENT, owner, world, stack, (ItemStack) null); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); + this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); + } + + public ThrownTrident(Level world, double x, double y, double z, ItemStack stack) { + super(EntityType.TRIDENT, x, y, z, world, stack, stack); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); + this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); + } +@@ -76,10 +82,10 @@ + } + } + +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause + } else { + if (!(entity instanceof Player) && this.position().distanceTo(entity.getEyePosition()) < (double) entity.getBbWidth() + 1.0D) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; + } + +@@ -109,8 +115,22 @@ + + public boolean isFoil() { + return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); ++ } ++ ++ // Paper start ++ public void setFoil(boolean foil) { ++ this.entityData.set(ThrownTrident.ID_FOIL, foil); + } + ++ public int getLoyalty() { ++ return this.entityData.get(ThrownTrident.ID_LOYALTY); ++ } ++ ++ public void setLoyalty(byte loyalty) { ++ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); ++ } ++ // Paper end ++ + @Nullable + @Override + protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { +@@ -120,7 +140,7 @@ + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity entity = entityHitResult.getEntity(); +- float f = 8.0F; ++ float f = (float) this.getBaseDamage(); // Paper - Allow trident custom damage + Entity entity1 = this.getOwner(); + DamageSource damagesource = this.damageSources().trident(this, (Entity) (entity1 == null ? this : entity1)); + Level world = this.level(); +@@ -137,7 +157,7 @@ + + world = this.level(); + if (world instanceof ServerLevel) { +- worldserver = (ServerLevel) world; ++ ServerLevel worldserver = (ServerLevel) world; // CraftBukkit - decompile error + EnchantmentHelper.doPostAttackEffectsWithItemSourceOnBreak(worldserver, entity, damagesource, this.getWeaponItem(), (item) -> { + this.kill(worldserver); + }); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch new file mode 100644 index 0000000000..96234163f1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/world/entity/projectile/WitherSkull.java ++++ b/net/minecraft/world/entity/projectile/WitherSkull.java +@@ -23,6 +23,10 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end + + public class WitherSkull extends AbstractHurtingProjectile { + +@@ -69,11 +73,11 @@ + if (entity.isAlive()) { + EnchantmentHelper.doPostAttackEffects(worldserver, entity, damagesource); + } else { +- entityliving.heal(5.0F); ++ entityliving.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); // CraftBukkit + } + } + } else { +- flag = entity.hurtServer(worldserver, this.damageSources().magic(), 5.0F); ++ flag = entity.hurtServer(worldserver, this.damageSources().magic().customEventDamager(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls // Paper - fix DamageSource API + } + + if (flag && entity instanceof LivingEntity entityliving) { +@@ -86,7 +90,7 @@ + } + + if (b0 > 0) { +- entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource()); ++ entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + +@@ -97,8 +101,16 @@ + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); + if (!this.level().isClientSide) { +- this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, Level.ExplosionInteraction.MOB); +- this.discard(); ++ // CraftBukkit start ++ // this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB); ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch new file mode 100644 index 0000000000..01844591e6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch @@ -0,0 +1,48 @@ +--- a/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java ++++ b/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java +@@ -24,6 +24,9 @@ + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public abstract class AbstractWindCharge extends AbstractHurtingProjectile implements ItemSupplier { + +@@ -98,7 +101,7 @@ + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) {} ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) {} // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + + public abstract void explode(Vec3 pos); + +@@ -111,7 +114,7 @@ + Vec3 vec3d1 = blockHitResult.getLocation().add(vec3d); + + this.explode(vec3d1); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -120,7 +123,7 @@ + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); + if (!this.level().isClientSide) { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + + } +@@ -155,7 +158,7 @@ + public void tick() { + if (!this.level().isClientSide && this.getBlockY() > this.level().getMaxY() + 30) { + this.explode(this.position()); +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause + } else { + super.tick(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch new file mode 100644 index 0000000000..789e719f42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch @@ -0,0 +1,174 @@ +--- a/net/minecraft/world/entity/raid/Raid.java ++++ b/net/minecraft/world/entity/raid/Raid.java +@@ -107,6 +107,11 @@ + private Raid.RaidStatus status; + private int celebrationTicks; + private Optional waveSpawnPos; ++ // Paper start ++ private static final String PDC_NBT_KEY = "BukkitValues"; ++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry PDC_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); ++ public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); ++ // Paper end + + public Raid(int id, ServerLevel world, BlockPos pos) { + this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); +@@ -150,6 +155,11 @@ + this.heroesOfTheVillage.add(NbtUtils.loadUUID(nbtbase)); + } + } ++ // Paper start ++ if (nbt.contains(PDC_NBT_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) { ++ this.persistentDataContainer.putAll(nbt.getCompound(PDC_NBT_KEY)); ++ } ++ // Paper end + + } + +@@ -177,6 +187,12 @@ + return this.status == Raid.RaidStatus.LOSS; + } + ++ // CraftBukkit start ++ public boolean isInProgress() { ++ return this.status == RaidStatus.ONGOING; ++ } ++ // CraftBukkit end ++ + public float getTotalHealth() { + return this.totalHealth; + } +@@ -281,6 +297,7 @@ + + this.active = this.level.hasChunkAt(this.center); + if (this.level.getDifficulty() == Difficulty.PEACEFUL) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.PEACE); // CraftBukkit + this.stop(); + return; + } +@@ -300,13 +317,16 @@ + if (!this.level.isVillage(this.center)) { + if (this.groupsSpawned > 0) { + this.status = Raid.RaidStatus.LOSS; ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(this, new java.util.ArrayList<>()); // CraftBukkit + } else { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.NOT_IN_VILLAGE); // CraftBukkit + this.stop(); + } + } + + ++this.ticksActive; + if (this.ticksActive >= 48000L) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.TIMEOUT); // CraftBukkit + this.stop(); + return; + } +@@ -374,6 +394,7 @@ + } + + if (j > 5) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit + this.stop(); + break; + } +@@ -386,6 +407,7 @@ + this.status = Raid.RaidStatus.VICTORY; + Iterator iterator = this.heroesOfTheVillage.iterator(); + ++ List winners = new java.util.ArrayList<>(); // CraftBukkit + while (iterator.hasNext()) { + UUID uuid = (UUID) iterator.next(); + Entity entity = this.level.getEntity(uuid); +@@ -400,10 +422,12 @@ + + entityplayer.awardStat(Stats.RAID_WIN); + CriteriaTriggers.RAID_WIN.trigger(entityplayer); ++ winners.add(entityplayer.getBukkitEntity()); // CraftBukkit + } + } + } + } ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(this, winners); // CraftBukkit + } + } + +@@ -411,6 +435,7 @@ + } else if (this.isOver()) { + ++this.celebrationTicks; + if (this.celebrationTicks >= 600) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.FINISHED); // CraftBukkit + this.stop(); + return; + } +@@ -544,6 +569,10 @@ + int j = araid_wave.length; + int k = 0; + ++ // CraftBukkit start ++ Raider leader = null; ++ List raiders = new java.util.ArrayList<>(); ++ // CraftBukkit end + while (k < j) { + Raid.RaiderType raid_wave = araid_wave[k]; + int l = this.getDefaultNumSpawns(raid_wave, i, flag1) + this.getPotentialBonusSpawns(raid_wave, this.random, i, difficultydamagescaler, flag1); +@@ -559,9 +588,11 @@ + entityraider.setPatrolLeader(true); + this.setLeader(i, entityraider); + flag = true; ++ leader = entityraider; // CraftBukkit + } + + this.joinRaid(i, entityraider, pos, false); ++ raiders.add(entityraider); // CraftBukkit + if (raid_wave.entityType == EntityType.RAVAGER) { + Raider entityraider1 = null; + +@@ -580,6 +611,7 @@ + this.joinRaid(i, entityraider1, pos, false); + entityraider1.moveTo(pos, 0.0F, 0.0F); + entityraider1.startRiding(entityraider); ++ raiders.add(entityraider); // CraftBukkit + } + } + +@@ -597,6 +629,7 @@ + ++this.groupsSpawned; + this.updateBossbar(); + this.setDirty(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidSpawnWaveEvent(this, leader, raiders); // CraftBukkit + } + + public void joinRaid(int wave, Raider raider, @Nullable BlockPos pos, boolean existing) { +@@ -612,7 +645,7 @@ + raider.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(pos), EntitySpawnReason.EVENT, (SpawnGroupData) null); + raider.applyRaidBuffs(this.level, wave, false); + raider.setOnGround(true); +- this.level.addFreshEntityWithPassengers(raider); ++ this.level.addFreshEntityWithPassengers(raider, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.RAID); // CraftBukkit + } + } + +@@ -839,6 +872,11 @@ + } + + nbt.put("HeroesOfTheVillage", nbttaglist); ++ // Paper start ++ if (!this.persistentDataContainer.isEmpty()) { ++ nbt.put(PDC_NBT_KEY, this.persistentDataContainer.toTagCompound()); ++ } ++ // Paper end + return nbt; + } + +@@ -865,6 +903,12 @@ + this.heroesOfTheVillage.add(entity.getUUID()); + } + ++ // CraftBukkit start - a method to get all raiders ++ public java.util.Collection getRaiders() { ++ return this.groupRaiderMap.values().stream().flatMap(Set::stream).collect(java.util.stream.Collectors.toSet()); ++ } ++ // CraftBukkit end ++ + private static enum RaidStatus { + + ONGOING, VICTORY, LOSS, STOPPED; diff --git a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch new file mode 100644 index 0000000000..7f33d0dc93 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch @@ -0,0 +1,83 @@ +--- a/net/minecraft/world/entity/raid/Raider.java ++++ b/net/minecraft/world/entity/raid/Raider.java +@@ -40,6 +40,9 @@ + import net.minecraft.world.level.ServerLevelAccessor; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public abstract class Raider extends PatrollingMonster { + +@@ -225,18 +228,25 @@ + boolean flag = this.hasActiveRaid() && this.getCurrentRaid().getLeader(this.getWave()) != null; + + if (this.hasActiveRaid() && !flag && ItemStack.matches(itemstack, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { ++ // Paper start - EntityPickupItemEvent fixes ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) { ++ return; ++ } ++ // Paper end - EntityPickupItemEvent fixes + EquipmentSlot enumitemslot = EquipmentSlot.HEAD; + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); + double d0 = (double) this.getEquipmentDropChance(enumitemslot); + + if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.spawnAtLocation(world, itemstack1); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + this.onItemPickup(itemEntity); + this.setItemSlot(enumitemslot, itemstack); + this.take(itemEntity, itemstack.getCount()); +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + this.getCurrentRaid().setLeader(this.getWave(), this); + this.setPatrolLeader(true); + } else { +@@ -290,7 +300,7 @@ + @Nullable + private ItemEntity pursuedBannerItemEntity; + +- public ObtainRaidLeaderBannerGoal(final Raider entityraider) { ++ public ObtainRaidLeaderBannerGoal(final T entityraider) { // CraftBukkit - decompile error + this.mob = entityraider; + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } +@@ -335,6 +345,7 @@ + } + + private boolean cannotPickUpBanner() { ++ if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + if (!this.mob.hasActiveRaid()) { + return true; + } else if (this.mob.getCurrentRaid().isOver()) { +@@ -518,7 +529,7 @@ + } + } + +- protected static class HoldGroundAttackGoal extends Goal { ++ public static class HoldGroundAttackGoal extends Goal { + + private final Raider mob; + private final float hostileRadiusSqr; +@@ -547,7 +558,7 @@ + while (iterator.hasNext()) { + Raider entityraider = (Raider) iterator.next(); + +- entityraider.setTarget(this.mob.getTarget()); ++ entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit + } + + } +@@ -564,7 +575,7 @@ + while (iterator.hasNext()) { + Raider entityraider = (Raider) iterator.next(); + +- entityraider.setTarget(entityliving); ++ entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit + entityraider.setAggressive(true); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch new file mode 100644 index 0000000000..b787b60fa1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/entity/raid/Raids.java ++++ b/net/minecraft/world/entity/raid/Raids.java +@@ -115,11 +115,23 @@ + + Raid raid = this.getOrCreateRaid(player.serverLevel(), blockposition2); + ++ /* CraftBukkit - moved down + if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) { + this.raidMap.put(raid.getId(), raid); + } ++ */ + +- if (!raid.isStarted() || raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel()) { ++ if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) { ++ player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN); ++ return null; ++ } ++ ++ if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) { ++ this.raidMap.put(raid.getId(), raid); ++ } ++ // CraftBukkit end + raid.absorbRaidOmen(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch new file mode 100644 index 0000000000..28b20b3791 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch @@ -0,0 +1,125 @@ +--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java ++++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java +@@ -47,6 +47,14 @@ + import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++import org.bukkit.event.vehicle.VehicleMoveEvent; ++// CraftBukkit end + + public abstract class AbstractBoat extends VehicleEntity implements Leashable { + +@@ -87,6 +95,14 @@ + private Leashable.LeashData leashData; + private final Supplier dropItem; + ++ // CraftBukkit start ++ // PAIL: 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; ++ // CraftBukkit end ++ + public AbstractBoat(EntityType type, Level world, Supplier itemSupplier) { + super(type, world); + this.dropItem = itemSupplier; +@@ -128,7 +144,7 @@ + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return true; + } + +@@ -180,11 +196,32 @@ + + @Override + public void push(Entity entity) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (entity instanceof AbstractBoat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { ++ // CraftBukkit start ++ if (!this.isPassengerOfSameVehicle(entity)) { ++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + super.push(entity); + } + } else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) { ++ // CraftBukkit start ++ if (!this.isPassengerOfSameVehicle(entity)) { ++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + super.push(entity); + } + +@@ -247,6 +284,7 @@ + return this.getDirection().getClockWise(); + } + ++ private Location lastLocation; // CraftBukkit + @Override + public void tick() { + this.oldStatus = this.status; +@@ -287,6 +325,21 @@ + this.setDeltaMovement(Vec3.ZERO); + } + ++ // CraftBukkit start ++ org.bukkit.Server server = this.level().getCraftServer(); ++ org.bukkit.World bworld = this.level().getWorld(); ++ ++ Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot()); ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ ++ server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle)); ++ ++ if (this.lastLocation != null && !this.lastLocation.equals(to)) { ++ VehicleMoveEvent event = new VehicleMoveEvent(vehicle, this.lastLocation, to); ++ server.getPluginManager().callEvent(event); ++ } ++ this.lastLocation = vehicle.getLocation(); ++ // CraftBukkit end + this.applyEffectsFromBlocks(); + this.applyEffectsFromBlocks(); + this.tickBubbleColumn(); +@@ -790,11 +843,18 @@ + + @Override + public void remove(Entity.RemovalReason reason) { +- if (!this.level().isClientSide && reason.shouldDestroy() && this.isLeashed()) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end ++ if (!this.level().isClientSide && entity_removalreason.shouldDestroy() && this.isLeashed()) { + this.dropLeash(); + } + +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch new file mode 100644 index 0000000000..f23f862b79 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch @@ -0,0 +1,121 @@ +--- a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java ++++ b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java +@@ -27,6 +27,15 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.LootTable; + ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end ++ + public abstract class AbstractChestBoat extends AbstractBoat implements HasCustomInventoryScreen, ContainerEntity { + + private static final int CONTAINER_SIZE = 27; +@@ -70,11 +79,18 @@ + + @Override + public void remove(Entity.RemovalReason reason) { +- if (!this.level().isClientSide && reason.shouldDestroy()) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end ++ if (!this.level().isClientSide && entity_removalreason.shouldDestroy()) { + Containers.dropContents(this.level(), (Entity) this, (Container) this); + } + +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause + } + + @Override +@@ -109,10 +125,10 @@ + + @Override + public void openCustomInventoryScreen(Player player) { +- player.openMenu(this); ++ // Paper - fix inventory open cancel - moved into below if + Level world = player.level(); + +- if (world instanceof ServerLevel worldserver) { ++ if (world instanceof ServerLevel worldserver && player.openMenu(this).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + this.gameEvent(GameEvent.CONTAINER_OPEN, player); + PiglinAi.angerNearbyPiglins(worldserver, player, true); + } +@@ -165,7 +181,7 @@ + @Nullable + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- if (this.lootTable != null && player.isSpectator()) { ++ if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that) + return null; + } else { + this.unpackLootTable(playerInventory.player); +@@ -212,4 +228,59 @@ + public void stopOpen(Player player) { + this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player)); + } ++ ++ // Paper start - LootTable API ++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); ++ ++ @Override ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ return this.lootableData; ++ } ++ // Paper end - LootTable API ++ // CraftBukkit start ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ @Override ++ public List getContents() { ++ return this.itemStacks; ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public InventoryHolder getOwner() { ++ org.bukkit.entity.Entity entity = this.getBukkitEntity(); ++ if (entity instanceof InventoryHolder) return (InventoryHolder) entity; ++ return null; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.getBukkitEntity().getLocation(); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch new file mode 100644 index 0000000000..e7b99cb727 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch @@ -0,0 +1,196 @@ +--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -42,6 +42,13 @@ + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++import org.bukkit.util.Vector; ++// CraftBukkit end + + public abstract class AbstractMinecart extends VehicleEntity { + +@@ -76,6 +83,18 @@ + enummap.put(RailShape.NORTH_EAST, Pair.of(baseblockposition2, baseblockposition1)); + }); + ++ // CraftBukkit start ++ public boolean slowWhenEmpty = true; ++ private double derailedX = 0.5; ++ private double derailedY = 0.5; ++ private double derailedZ = 0.5; ++ private double flyingX = 0.95; ++ private double flyingY = 0.95; ++ private double flyingZ = 0.95; ++ public Double maxSpeed; ++ // CraftBukkit end ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API ++ + protected AbstractMinecart(EntityType type, Level world) { + super(type, world); + this.blocksBuilding = true; +@@ -101,7 +120,7 @@ + + @Nullable + public static T createMinecart(Level world, double x, double y, double z, EntityType type, EntitySpawnReason reason, ItemStack stack, @Nullable Player player) { +- T t0 = (AbstractMinecart) type.create(world, reason); ++ T t0 = (T) type.create(world, reason); // CraftBukkit - decompile error + + if (t0 != null) { + t0.setInitialPos(x, y, z); +@@ -139,11 +158,19 @@ + + @Override + public boolean canCollideWith(Entity other) { +- return AbstractBoat.canVehicleCollide(this, other); ++ // Paper start - fix VehicleEntityCollisionEvent not called when colliding with player ++ boolean collides = AbstractBoat.canVehicleCollide(this, other); ++ if (!collides) { ++ return false; ++ } ++ org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), other.getBukkitEntity()); ++ ++ return collisionEvent.callEvent(); ++ // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return true; + } + +@@ -262,6 +289,14 @@ + + @Override + public void tick() { ++ // CraftBukkit start ++ double prevX = this.getX(); ++ double prevY = this.getY(); ++ double prevZ = this.getZ(); ++ float prevYaw = this.getYRot(); ++ float prevPitch = this.getXRot(); ++ // CraftBukkit end ++ + if (this.getHurtTime() > 0) { + this.setHurtTime(this.getHurtTime() - 1); + } +@@ -271,8 +306,20 @@ + } + + this.checkBelowWorld(); +- this.handlePortal(); ++ // this.handlePortal(); // CraftBukkit - handled in postTick + this.behavior.tick(); ++ // CraftBukkit start ++ org.bukkit.World bworld = this.level().getWorld(); ++ Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch); ++ Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot()); ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ ++ this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle)); ++ ++ if (!from.equals(to)) { ++ this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to)); ++ } ++ // CraftBukkit end + this.updateInWaterStateAndDoFluidPushing(); + if (this.isInLava()) { + this.lavaHurt(); +@@ -385,12 +432,16 @@ + + this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); + if (this.onGround()) { +- this.setDeltaMovement(this.getDeltaMovement().scale(0.5D)); ++ // CraftBukkit start - replace magic numbers with our variables ++ this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ)); ++ // CraftBukkit end + } + + this.move(MoverType.SELF, this.getDeltaMovement()); + if (!this.onGround()) { +- this.setDeltaMovement(this.getDeltaMovement().scale(0.95D)); ++ // CraftBukkit start - replace magic numbers with our variables ++ this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.flyingX, this.getDeltaMovement().y * this.flyingY, this.getDeltaMovement().z * this.flyingZ)); ++ // CraftBukkit end + } + + } +@@ -502,6 +553,16 @@ + + this.flipped = nbt.getBoolean("FlippedRotation"); + this.firstTick = nbt.getBoolean("HasTicked"); ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + } + + @Override +@@ -514,13 +575,28 @@ + + nbt.putBoolean("FlippedRotation", this.flipped); + nbt.putBoolean("HasTicked", this.firstTick); ++ ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + } + + @Override + public void push(Entity entity) { + if (!this.level().isClientSide) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (!this.hasPassenger(entity)) { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = d0 * d0 + d1 * d1; +@@ -645,4 +721,27 @@ + public boolean isFurnace() { + return false; + } ++ ++ // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers ++ public Vector getFlyingVelocityMod() { ++ return new Vector(this.flyingX, this.flyingY, this.flyingZ); ++ } ++ ++ public void setFlyingVelocityMod(Vector flying) { ++ this.flyingX = flying.getX(); ++ this.flyingY = flying.getY(); ++ this.flyingZ = flying.getZ(); ++ } ++ ++ public Vector getDerailedVelocityMod() { ++ return new Vector(this.derailedX, this.derailedY, this.derailedZ); ++ } ++ ++ public void setDerailedVelocityMod(Vector derailed) { ++ this.derailedX = derailed.getX(); ++ this.derailedY = derailed.getY(); ++ this.derailedZ = derailed.getZ(); ++ } ++ // CraftBukkit end ++ public net.minecraft.world.item.Item publicGetDropItem() { return getDropItem(); } // Paper - api to get boat and minecart material - expose public drop item + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch new file mode 100644 index 0000000000..b33136b346 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch @@ -0,0 +1,98 @@ +--- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java ++++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +@@ -20,6 +20,14 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.storage.loot.LootTable; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end + + public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity { + +@@ -28,9 +36,58 @@ + public ResourceKey lootTable; + public long lootTableSeed; + ++ // Paper start - LootTable API ++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); ++ ++ @Override ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ return this.lootableData; ++ } ++ // Paper end - LootTable API ++ // CraftBukkit start ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.itemStacks; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ public InventoryHolder getOwner() { ++ org.bukkit.entity.Entity cart = this.getBukkitEntity(); ++ if(cart instanceof InventoryHolder) return (InventoryHolder) cart; ++ return null; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.getBukkitEntity().getLocation(); ++ } ++ // CraftBukkit end ++ + protected AbstractMinecartContainer(EntityType type, Level world) { + super(type, world); +- this.itemStacks = NonNullList.withSize(36, ItemStack.EMPTY); ++ this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 + } + + @Override +@@ -74,11 +131,18 @@ + + @Override + public void remove(Entity.RemovalReason reason) { +- if (!this.level().isClientSide && reason.shouldDestroy()) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.remove(reason, null); ++ } ++ ++ @Override ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ // CraftBukkit end ++ if (!this.level().isClientSide && entity_removalreason.shouldDestroy()) { + Containers.dropContents(this.level(), (Entity) this, (Container) this); + } + +- super.remove(reason); ++ super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch new file mode 100644 index 0000000000..3ac071ee7d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/world/entity/vehicle/ContainerEntity.java ++++ b/net/minecraft/world/entity/vehicle/ContainerEntity.java +@@ -62,22 +62,26 @@ + default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) { + if (this.getContainerLootTable() != null) { + nbt.putString("LootTable", this.getContainerLootTable().location().toString()); ++ this.lootableData().saveNbt(nbt); // Paper + if (this.getContainerLootTableSeed() != 0L) { + nbt.putLong("LootTableSeed", this.getContainerLootTableSeed()); + } +- } else { +- ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); + } ++ ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain + } + + default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) { + this.clearItemStacks(); + if (nbt.contains("LootTable", 8)) { +- this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); ++ this.setContainerLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation ++ // Paper start - LootTable API ++ if (this.getContainerLootTable() != null) { ++ this.lootableData().loadNbt(nbt); ++ } ++ // Paper end - LootTable API + this.setContainerLootTableSeed(nbt.getLong("LootTableSeed")); +- } else { +- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); + } ++ ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain + } + + default void chestVehicleDestroyed(DamageSource source, ServerLevel world, Entity vehicle) { +@@ -91,19 +95,28 @@ + } + + default InteractionResult interactWithContainerVehicle(Player player) { +- player.openMenu(this); ++ // Paper start - Fix InventoryOpenEvent cancellation ++ if (player.openMenu(this).isEmpty()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end - Fix InventoryOpenEvent cancellation + return InteractionResult.SUCCESS; + } + + default void unpackChestVehicleLootTable(@Nullable Player player) { + MinecraftServer minecraftServer = this.level().getServer(); +- if (this.getContainerLootTable() != null && minecraftServer != null) { ++ if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API + LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getContainerLootTable()); + if (player != null) { + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getContainerLootTable()); + } + +- this.setContainerLootTable(null); ++ // Paper start - LootTable API ++ if (this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { ++ this.setContainerLootTable(null); ++ } ++ // Paper end - LootTable API ++ + LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position()); + if (player != null) { + builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player); +@@ -173,4 +186,14 @@ + default boolean isChestVehicleStillValid(Player player) { + return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0); + } ++ ++ // Paper start - LootTable API ++ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ throw new UnsupportedOperationException("Implement this method"); ++ } ++ ++ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() { ++ return ((com.destroystokyo.paper.loottable.PaperLootableInventory) ((net.minecraft.world.entity.Entity) this).getBukkitEntity()); ++ } ++ // Paper end - LootTable API + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch new file mode 100644 index 0000000000..7c789e80ad --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java ++++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +@@ -128,12 +128,19 @@ + + @Override + public CommandSourceStack createCommandSourceStack() { +- return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), 2, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); ++ return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); // Paper - configurable command block perm level + } + + @Override + public boolean isValid() { + return !MinecartCommandBlock.this.isRemoved(); + } ++ ++ // CraftBukkit start ++ @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return (org.bukkit.craftbukkit.entity.CraftMinecartCommand) MinecartCommandBlock.this.getBukkitEntity(); ++ } ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch new file mode 100644 index 0000000000..7fdf216d8a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/entity/vehicle/MinecartTNT.java ++++ b/net/minecraft/world/entity/vehicle/MinecartTNT.java +@@ -25,6 +25,10 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.ExplosionPrimeEvent; ++// CraftBukkit end + + public class MinecartTNT extends AbstractMinecart { + +@@ -37,6 +41,7 @@ + public int fuse = -1; + public float explosionPowerBase = 4.0F; + public float explosionSpeedFactor = 1.0F; ++ public boolean isIncendiary = false; // CraftBukkit - add field + + public MinecartTNT(EntityType type, Level world) { + super(type, world); +@@ -51,6 +56,12 @@ + public void tick() { + super.tick(); + if (this.fuse > 0) { ++ // Paper start - Configurable TNT height nerf ++ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); ++ return; ++ } ++ // Paper end - Configurable TNT height nerf + --this.fuse; + this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D); + } else if (this.fuse == 0) { +@@ -117,8 +128,16 @@ + if (world instanceof ServerLevel worldserver) { + double d1 = Math.min(Math.sqrt(power), 5.0D); + +- worldserver.explode(this, damageSource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * d1), false, Level.ExplosionInteraction.TNT); +- this.discard(); ++ // CraftBukkit start ++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * d1), this.isIncendiary); ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.fuse = -1; ++ return; ++ } ++ worldserver.explode(this, damageSource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.TNT); ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch new file mode 100644 index 0000000000..8bed619b55 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch @@ -0,0 +1,83 @@ +--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +@@ -27,6 +27,10 @@ + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++// CraftBukkit end + + public class NewMinecartBehavior extends MinecartBehavior { + +@@ -516,6 +520,12 @@ + + @Override + public double getMaxSpeed(ServerLevel world) { ++ // CraftBukkit start ++ Double maxSpeed = this.minecart.maxSpeed; ++ if (maxSpeed != null) { ++ return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed); ++ } ++ // CraftBukkit end + return (double) world.getGameRules().getInt(GameRules.RULE_MINECART_MAX_SPEED) * (this.minecart.isInWater() ? 0.5D : 1.0D) / 20.0D; + } + +@@ -544,7 +554,8 @@ + + @Override + public double getSlowdownFactor() { +- return this.minecart.isVehicle() ? 0.997D : 0.975D; ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper ++ return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty + } + + @Override +@@ -571,6 +582,14 @@ + Entity entity = (Entity) iterator.next(); + + if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.minecart.isVehicle() && !entity.isPassenger()) { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + boolean flag = entity.startRiding(this.minecart); + + if (flag) { +@@ -597,6 +616,16 @@ + Entity entity = (Entity) iterator.next(); + + if (entity instanceof Player || entity instanceof IronGolem || entity instanceof AbstractMinecart || this.minecart.isVehicle() || entity.isPassenger()) { ++ // CraftBukkit start ++ if (!this.minecart.isPassengerOfSameVehicle(entity)) { ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ } ++ // CraftBukkit end + entity.push((Entity) this.minecart); + flag = true; + } +@@ -609,6 +638,14 @@ + Entity entity1 = (Entity) iterator1.next(); + + if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity1.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + entity1.push((Entity) this.minecart); + flag = true; + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch new file mode 100644 index 0000000000..5f791f5901 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch @@ -0,0 +1,75 @@ +--- a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +@@ -24,6 +24,10 @@ + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; ++// CraftBukkit end + + public class OldMinecartBehavior extends MinecartBehavior { + +@@ -454,8 +458,26 @@ + Entity entity = (Entity) iterator.next(); + + if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.minecart.isVehicle() && !entity.isPassenger()) { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + entity.startRiding(this.minecart); + } else { ++ // CraftBukkit start ++ if (!this.minecart.isPassengerOfSameVehicle(entity)) { ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ } ++ // CraftBukkit end + entity.push((Entity) this.minecart); + } + } +@@ -467,6 +489,14 @@ + Entity entity1 = (Entity) iterator1.next(); + + if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) { ++ // CraftBukkit start ++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity1.getBukkitEntity()); ++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent); ++ ++ if (collisionEvent.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end + entity1.push((Entity) this.minecart); + } + } +@@ -487,11 +517,18 @@ + + @Override + public double getMaxSpeed(ServerLevel world) { ++ // CraftBukkit start ++ Double maxSpeed = this.minecart.maxSpeed; ++ if (maxSpeed != null) { ++ return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed); ++ } ++ // CraftBukkit end + return this.minecart.isInWater() ? 0.2D : 0.4D; + } + + @Override + public double getSlowdownFactor() { +- return this.minecart.isVehicle() ? 0.997D : 0.96D; ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper ++ return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch new file mode 100644 index 0000000000..10fce15f79 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch @@ -0,0 +1,64 @@ +--- a/net/minecraft/world/entity/vehicle/VehicleEntity.java ++++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java +@@ -17,6 +17,13 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.vehicle.VehicleDamageEvent; ++import org.bukkit.event.vehicle.VehicleDestroyEvent; ++// CraftBukkit end ++ + public abstract class VehicleEntity extends Entity { + + protected static final EntityDataAccessor DATA_ID_HURT = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.INT); +@@ -40,6 +47,18 @@ + return false; + } else { + boolean flag; ++ // CraftBukkit start ++ Vehicle vehicle = (Vehicle) this.getBukkitEntity(); ++ org.bukkit.entity.Entity attacker = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity(); ++ ++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, (double) amount); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ amount = (float) event.getDamage(); ++ // CraftBukkit end + label32: + { + this.setHurtDir(-this.getHurtDir()); +@@ -65,9 +84,27 @@ + + if ((flag1 || this.getDamage() <= 40.0F) && !this.shouldSourceDestroy(source)) { + if (flag1) { +- this.discard(); ++ // CraftBukkit start ++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker); ++ this.level().getCraftServer().getPluginManager().callEvent(destroyEvent); ++ ++ if (destroyEvent.isCancelled()) { ++ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away ++ return true; ++ } ++ // CraftBukkit end ++ this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + } + } else { ++ // CraftBukkit start ++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker); ++ this.level().getCraftServer().getPluginManager().callEvent(destroyEvent); ++ ++ if (destroyEvent.isCancelled()) { ++ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away ++ return true; ++ } ++ // CraftBukkit end + this.destroy(world, source); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch b/paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch new file mode 100644 index 0000000000..68a35b8aa4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch @@ -0,0 +1,101 @@ +--- a/net/minecraft/world/food/FoodData.java ++++ b/net/minecraft/world/food/FoodData.java +@@ -1,11 +1,14 @@ + package net.minecraft.world.food; + ++import net.minecraft.world.level.GameRules; + import net.minecraft.nbt.CompoundTag; ++import net.minecraft.network.protocol.game.ClientboundSetHealthPacket; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; + import net.minecraft.world.Difficulty; +-import net.minecraft.world.level.GameRules; ++import net.minecraft.world.item.ItemStack; ++// CraftBukkit end + + public class FoodData { + +@@ -13,6 +16,11 @@ + public float saturationLevel = 5.0F; + public float exhaustionLevel; + private int tickTimer; ++ // CraftBukkit start ++ public int saturatedRegenRate = 10; ++ public int unsaturatedRegenRate = 80; ++ public int starvationRate = 80; ++ // CraftBukkit end + + public FoodData() {} + +@@ -29,6 +37,20 @@ + this.add(foodComponent.nutrition(), foodComponent.saturation()); + } + ++ // CraftBukkit start ++ public void eat(FoodProperties foodinfo, ItemStack itemstack, ServerPlayer entityplayer) { ++ int oldFoodLevel = this.foodLevel; ++ ++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityplayer, foodinfo.nutrition() + oldFoodLevel, itemstack); ++ ++ if (!event.isCancelled()) { ++ this.add(event.getFoodLevel() - oldFoodLevel, foodinfo.saturation()); ++ } ++ ++ entityplayer.getBukkitEntity().sendHealthUpdate(); ++ } ++ // CraftBukkit end ++ + public void tick(ServerPlayer player) { + ServerLevel worldserver = player.serverLevel(); + Difficulty enumdifficulty = worldserver.getDifficulty(); +@@ -38,7 +60,15 @@ + if (this.saturationLevel > 0.0F) { + this.saturationLevel = Math.max(this.saturationLevel - 1.0F, 0.0F); + } else if (enumdifficulty != Difficulty.PEACEFUL) { +- this.foodLevel = Math.max(this.foodLevel - 1, 0); ++ // CraftBukkit start ++ 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(); ++ } ++ ++ player.connection.send(new ClientboundSetHealthPacket(player.getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel)); ++ // CraftBukkit end + } + } + +@@ -46,23 +76,25 @@ + + if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) { + ++this.tickTimer; +- if (this.tickTimer >= 10) { ++ if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit + float f = Math.min(this.saturationLevel, 6.0F); + +- player.heal(f / 6.0F); +- this.addExhaustion(f); ++ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen ++ // this.addExhaustion(f); CraftBukkit - EntityExhaustionEvent ++ player.causeFoodExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent + this.tickTimer = 0; + } + } else if (flag && this.foodLevel >= 18 && player.isHurt()) { + ++this.tickTimer; +- if (this.tickTimer >= 80) { +- player.heal(1.0F); +- this.addExhaustion(6.0F); ++ if (this.tickTimer >= this.unsaturatedRegenRate) { // CraftBukkit - add regen rate manipulation ++ player.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason ++ // this.addExhaustion(6.0F); CraftBukkit - EntityExhaustionEvent ++ player.causeFoodExhaustion(player.level().spigotConfig.regenExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + this.tickTimer = 0; + } + } else if (this.foodLevel <= 0) { + ++this.tickTimer; +- if (this.tickTimer >= 80) { ++ if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation + if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { + player.hurtServer(worldserver, player.damageSources().starve(), 1.0F); + } diff --git a/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch b/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch new file mode 100644 index 0000000000..a8c85757de --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/food/FoodProperties.java ++++ b/net/minecraft/world/food/FoodProperties.java +@@ -5,6 +5,7 @@ + import net.minecraft.network.RegistryFriendlyByteBuf; + import net.minecraft.network.codec.ByteBufCodecs; + import net.minecraft.network.codec.StreamCodec; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -31,7 +32,7 @@ + + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), (SoundEvent) consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, randomsource.triangle(1.0F, 0.4F)); + if (user instanceof Player entityhuman) { +- entityhuman.getFoodData().eat(this); ++ entityhuman.getFoodData().eat(this, stack, (ServerPlayer) entityhuman); // CraftBukkit + world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch new file mode 100644 index 0000000000..a0c2f5bf6b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch @@ -0,0 +1,310 @@ +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -21,6 +21,8 @@ + import net.minecraft.ReportedException; + import net.minecraft.core.NonNullList; + import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.network.chat.Component; ++import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.Mth; + import net.minecraft.world.Container; +@@ -35,6 +37,18 @@ + import net.minecraft.world.level.block.entity.BlockEntity; + import org.slf4j.Logger; + ++// CraftBukkit start ++import com.google.common.base.Preconditions; ++import java.util.HashMap; ++import java.util.Map; ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.Event.Result; ++import org.bukkit.event.inventory.InventoryDragEvent; ++import org.bukkit.event.inventory.InventoryType; ++import org.bukkit.inventory.InventoryView; ++// CraftBukkit end ++ + public abstract class AbstractContainerMenu { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -67,6 +81,32 @@ + private ContainerSynchronizer synchronizer; + private boolean suppressRemoteUpdates; + ++ // CraftBukkit start ++ public boolean checkReachable = true; ++ public abstract InventoryView getBukkitView(); ++ public void transferTo(AbstractContainerMenu other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) { ++ InventoryView source = this.getBukkitView(), destination = other.getBukkitView(); ++ ((CraftInventory) source.getTopInventory()).getInventory().onClose(player); ++ ((CraftInventory) source.getBottomInventory()).getInventory().onClose(player); ++ ((CraftInventory) destination.getTopInventory()).getInventory().onOpen(player); ++ ((CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player); ++ } ++ private Component title; ++ public final Component getTitle() { ++ // Paper start - return chat component with empty text instead of throwing error ++ // Preconditions.checkState(this.title != null, "Title not set"); ++ if (this.title == null){ ++ return Component.literal(""); ++ } ++ // Paper end - return chat component with empty text instead of throwing error ++ return this.title; ++ } ++ public final void setTitle(Component title) { ++ Preconditions.checkState(this.title == null, "Title already set"); ++ this.title = title; ++ } ++ // CraftBukkit end ++ + protected AbstractContainerMenu(@Nullable MenuType type, int syncId) { + this.carried = ItemStack.EMPTY; + this.remoteSlots = NonNullList.create(); +@@ -188,10 +228,20 @@ + + if (this.synchronizer != null) { + this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); ++ this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot + } + + } + ++ // CraftBukkit start ++ public void broadcastCarriedItem() { ++ this.remoteCarried = this.getCarried().copy(); ++ if (this.synchronizer != null) { ++ this.synchronizer.sendCarriedChange(this, this.remoteCarried); ++ } ++ } ++ // CraftBukkit end ++ + public void removeSlotListener(ContainerListener listener) { + this.containerListeners.remove(listener); + } +@@ -281,7 +331,7 @@ + while (iterator.hasNext()) { + ContainerListener icrafting = (ContainerListener) iterator.next(); + +- icrafting.slotChanged(this, slot, itemstack2); ++ icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent + } + } + +@@ -410,6 +460,7 @@ + this.resetQuickCraft(); + } + } else if (this.quickcraftStatus == 1) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + slot = (Slot) this.slots.get(slotIndex); + itemstack = this.getCarried(); + if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { +@@ -417,7 +468,7 @@ + } + } else if (this.quickcraftStatus == 2) { + if (!this.quickcraftSlots.isEmpty()) { +- if (this.quickcraftSlots.size() == 1) { ++ if (this.quickcraftSlots.size() == 1) { // Paper - Fix CraftBukkit drag system + k = ((Slot) this.quickcraftSlots.iterator().next()).index; + this.resetQuickCraft(); + this.doClick(k, this.quickcraftType, ClickType.PICKUP, player); +@@ -433,6 +484,7 @@ + l = this.getCarried().getCount(); + Iterator iterator = this.quickcraftSlots.iterator(); + ++ Map draggedSlots = new HashMap(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack) + while (iterator.hasNext()) { + Slot slot1 = (Slot) iterator.next(); + ItemStack itemstack2 = this.getCarried(); +@@ -443,12 +495,48 @@ + int l1 = Math.min(AbstractContainerMenu.getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemstack1) + j1, k1); + + l -= l1 - j1; +- slot1.setByPlayer(itemstack1.copyWithCount(l1)); ++ // slot1.setByPlayer(itemstack1.copyWithCount(l1)); ++ draggedSlots.put(slot1.index, itemstack1.copyWithCount(l1)); // CraftBukkit - Put in map instead of setting + } + } + +- itemstack1.setCount(l); +- this.setCarried(itemstack1); ++ // CraftBukkit start - InventoryDragEvent ++ InventoryView view = this.getBukkitView(); ++ org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack1); ++ newcursor.setAmount(l); ++ 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 = this.getCarried(); ++ this.setCarried(CraftItemStack.asNMSCopy(newcursor)); ++ ++ InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.quickcraftType == 1, eventmap); ++ player.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ // Whether or not a change was made to the inventory that requires an update. ++ boolean needsUpdate = event.getResult() != Result.DEFAULT; ++ ++ if (event.getResult() != 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 (this.getCarried() != null) { ++ this.setCarried(CraftItemStack.asNMSCopy(event.getCursor())); ++ needsUpdate = true; ++ } ++ } else { ++ this.setCarried(oldCursor); ++ } ++ ++ if (needsUpdate && player instanceof ServerPlayer) { ++ this.sendAllDataToRemote(); ++ } ++ // CraftBukkit end + } + + this.resetQuickCraft(); +@@ -466,8 +554,11 @@ + if (slotIndex == -999) { + if (!this.getCarried().isEmpty()) { + if (clickaction == ClickAction.PRIMARY) { +- player.drop(this.getCarried(), true); ++ // CraftBukkit start ++ ItemStack carried = this.getCarried(); + this.setCarried(ItemStack.EMPTY); ++ player.drop(carried, true); ++ // CraftBukkit start + } else { + player.drop(this.getCarried().split(1), true); + } +@@ -530,11 +621,21 @@ + } + + slot.setChanged(); ++ // CraftBukkit start - Make sure the client has the right slot contents ++ if (player instanceof ServerPlayer && slot.getMaxStackSize() != 64) { ++ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem())); ++ // Updating a crafting inventory makes the client reset the result slot, have to send it again ++ if (this.getBukkitView().getType() == InventoryType.WORKBENCH || this.getBukkitView().getType() == InventoryType.CRAFTING) { ++ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem())); ++ } ++ } ++ // CraftBukkit end + } + } else { + int j2; + + if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + ItemStack itemstack4 = playerinventory.getItem(button); + + slot = (Slot) this.slots.get(slotIndex); +@@ -662,8 +763,9 @@ + ItemStack itemstack = this.getCarried(); + + if (!itemstack.isEmpty()) { ++ this.setCarried(ItemStack.EMPTY); // CraftBukkit - SPIGOT-4556 - from below + AbstractContainerMenu.dropOrPlaceInInventory(player, itemstack); +- this.setCarried(ItemStack.EMPTY); ++ // this.setCarried(ItemStack.EMPTY); // CraftBukkit - moved up + } + + } +@@ -729,6 +831,14 @@ + public abstract boolean stillValid(Player player); + + protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); ++ } ++ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { ++ if (isCheck) { ++ stack = stack.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + boolean flag1 = false; + int k = startIndex; + +@@ -752,6 +862,11 @@ + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(stack, itemstack1)) { + l = itemstack1.getCount() + stack.getCount(); + int i1 = slot.getMaxStackSize(itemstack1); +@@ -759,12 +874,16 @@ + if (l <= i1) { + stack.setCount(0); + itemstack1.setCount(l); ++ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + } else if (itemstack1.getCount() < i1) { + stack.shrink(i1 - itemstack1.getCount()); + itemstack1.setCount(i1); ++ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + } + } +@@ -795,10 +914,21 @@ + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (itemstack1.isEmpty() && slot.mayPlace(stack)) { + l = slot.getMaxStackSize(stack); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ if (isCheck) { ++ stack.shrink(Math.min(stack.getCount(), l)); ++ } else { ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setByPlayer(stack.split(Math.min(stack.getCount(), l))); + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + break; + } +@@ -893,6 +1023,11 @@ + } + + public ItemStack getCarried() { ++ // CraftBukkit start ++ if (this.carried.isEmpty()) { ++ this.setCarried(ItemStack.EMPTY); ++ } ++ // CraftBukkit end + return this.carried; + } + +@@ -947,4 +1082,15 @@ + this.stateId = this.stateId + 1 & 32767; + return this.stateId; + } ++ ++ // Paper start - Add missing InventoryHolders ++ // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question ++ // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily ++ // initializes the InventoryHolder safely. ++ protected final Supplier createBlockHolder(final ContainerLevelAccess context) { ++ //noinspection ConstantValue ++ Preconditions.checkArgument(context != null, "context was null"); ++ return () -> context.createBlockHolder(this); ++ } ++ // Paper end - Add missing InventoryHolders + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch new file mode 100644 index 0000000000..bda213246a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/inventory/AbstractCraftingMenu.java ++++ b/net/minecraft/world/inventory/AbstractCraftingMenu.java +@@ -13,14 +13,17 @@ + + private final int width; + private final int height; +- public final CraftingContainer craftSlots; ++ public final TransientCraftingContainer craftSlots; // CraftBukkit + public final ResultContainer resultSlots = new ResultContainer(); + +- public AbstractCraftingMenu(MenuType type, int syncId, int width, int height) { +- super(type, syncId); +- this.width = width; +- this.height = height; +- this.craftSlots = new TransientCraftingContainer(this, width, height); ++ public AbstractCraftingMenu(MenuType containers, int i, int j, int k, Inventory playerInventory) { // CraftBukkit ++ super(containers, i); ++ this.width = j; ++ this.height = k; ++ // CraftBukkit start ++ this.craftSlots = new TransientCraftingContainer(this, j, k, playerInventory.player); // CraftBukkit - pass player ++ this.craftSlots.resultInventory = this.resultSlots; // CraftBukkit - let InventoryCrafting know about its result slot ++ // CraftBukkit end + } + + protected Slot addResultSlot(Player player, int x, int y) { +@@ -38,7 +41,7 @@ + + @Override + public RecipeBookMenu.PostPlaceAction handlePlacement(boolean craftAll, boolean creative, RecipeHolder recipe, ServerLevel world, Inventory inventory) { +- RecipeHolder recipeholder1 = recipe; ++ RecipeHolder recipeholder1 = (RecipeHolder) recipe; // CraftBukkit - decompile error + + this.beginPlacingRecipe(); + +@@ -65,7 +68,7 @@ + } + }, this.width, this.height, list, list, inventory, recipeholder1, craftAll, creative); + } finally { +- this.finishPlacingRecipe(world, recipe); ++ this.finishPlacingRecipe(world, recipeholder1); // CraftBukkit - decompile error + } + + return containerrecipebook_a; diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch new file mode 100644 index 0000000000..42a568418f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java ++++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java +@@ -17,6 +17,10 @@ + import net.minecraft.world.item.crafting.RecipeType; + import net.minecraft.world.item.crafting.SingleRecipeInput; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; ++import org.bukkit.craftbukkit.inventory.CraftInventoryFurnace; ++import org.bukkit.craftbukkit.inventory.view.CraftFurnaceView; ++// CraftBukkit end + + public abstract class AbstractFurnaceMenu extends RecipeBookMenu { + +@@ -36,6 +40,22 @@ + private final RecipePropertySet acceptedInputs; + private final RecipeBookType recipeBookType; + ++ // CraftBukkit start ++ private CraftFurnaceView bukkitEntity = null; ++ private Inventory player; ++ ++ @Override ++ public CraftFurnaceView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryFurnace inventory = new CraftInventoryFurnace((AbstractFurnaceBlockEntity) this.container); ++ this.bukkitEntity = new CraftFurnaceView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + protected AbstractFurnaceMenu(MenuType type, RecipeType recipeType, ResourceKey recipePropertySetKey, RecipeBookType category, int syncId, Inventory platerInventory) { + this(type, recipeType, recipePropertySetKey, category, syncId, platerInventory, new SimpleContainer(3), new SimpleContainerData(4)); + } +@@ -53,6 +73,7 @@ + this.addSlot(new Slot(inventory, 0, 56, 17)); + this.addSlot(new FurnaceFuelSlot(this, inventory, 1, 56, 53)); + this.addSlot(new FurnaceResultSlot(platerInventory.player, inventory, 2, 116, 35)); ++ this.player = platerInventory; // CraftBukkit - save player + this.addStandardInventorySlots(platerInventory, 8, 84); + this.addDataSlots(propertyDelegate); + } +@@ -71,6 +92,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.container.stillValid(player); + } + +@@ -180,6 +202,6 @@ + public boolean recipeMatches(RecipeHolder entry) { + return ((AbstractCookingRecipe) entry.value()).matches(new SingleRecipeInput(AbstractFurnaceMenu.this.container.getItem(0)), world); + } +- }, 1, 1, List.of(this.getSlot(0)), list, inventory, recipe, craftAll, creative); ++ }, 1, 1, List.of(this.getSlot(0)), list, inventory, (RecipeHolder) recipe, craftAll, creative); // CraftBukkit - decompile error + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch new file mode 100644 index 0000000000..1c6f48d25b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch @@ -0,0 +1,166 @@ +--- a/net/minecraft/world/inventory/AnvilMenu.java ++++ b/net/minecraft/world/inventory/AnvilMenu.java +@@ -21,6 +21,10 @@ + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.view.CraftAnvilView; ++// CraftBukkit end ++ + public class AnvilMenu extends ItemCombinerMenu { + + public static final int INPUT_SLOT = 0; +@@ -45,6 +49,12 @@ + private static final int ADDITIONAL_SLOT_X_PLACEMENT = 76; + private static final int RESULT_SLOT_X_PLACEMENT = 134; + private static final int SLOT_Y_PLACEMENT = 47; ++ // CraftBukkit start ++ public static final int DEFAULT_DENIED_COST = -1; ++ public int maximumRepairCost = 40; ++ private CraftAnvilView bukkitEntity; ++ // CraftBukkit end ++ public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions + + public AnvilMenu(int syncId, Inventory inventory) { + this(syncId, inventory, ContainerLevelAccess.NULL); +@@ -72,7 +82,7 @@ + + @Override + protected boolean mayPickup(Player player, boolean present) { +- return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > 0; ++ return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item + } + + @Override +@@ -94,7 +104,7 @@ + this.inputSlots.setItem(1, ItemStack.EMPTY); + } + +- this.cost.set(0); ++ this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item + this.inputSlots.setItem(0, ItemStack.EMPTY); + this.access.execute((world, blockposition) -> { + BlockState iblockdata = world.getBlockState(blockposition); +@@ -102,6 +112,16 @@ + if (!player.hasInfiniteMaterials() && iblockdata.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) { + BlockState iblockdata1 = AnvilBlock.damage(iblockdata); + ++ // Paper start - AnvilDamageEvent ++ com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null); ++ if (!event.callEvent()) { ++ return; ++ } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) { ++ iblockdata1 = null; ++ } else { ++ iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, iblockdata.getValue(AnvilBlock.FACING)); ++ } ++ // Paper end - AnvilDamageEvent + if (iblockdata1 == null) { + world.removeBlock(blockposition, false); + world.levelEvent(1029, blockposition, 0); +@@ -143,8 +163,8 @@ + if (itemstack1.isDamageableItem() && itemstack.isValidRepairItem(itemstack2)) { + k = Math.min(itemstack1.getDamageValue(), itemstack1.getMaxDamage() / 4); + if (k <= 0) { +- this.resultSlots.setItem(0, ItemStack.EMPTY); +- this.cost.set(0); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit ++ this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item + return; + } + +@@ -158,8 +178,8 @@ + this.repairItemCountCost = i1; + } else { + if (!flag && (!itemstack1.is(itemstack2.getItem()) || !itemstack1.isDamageableItem())) { +- this.resultSlots.setItem(0, ItemStack.EMPTY); +- this.cost.set(0); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit ++ this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item + return; + } + +@@ -214,7 +234,7 @@ + flag2 = true; + } else { + flag1 = true; +- if (i2 > enchantment.getMaxLevel()) { ++ if (i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions + i2 = enchantment.getMaxLevel(); + } + +@@ -233,8 +253,8 @@ + } + + if (flag2 && !flag1) { +- this.resultSlots.setItem(0, ItemStack.EMPTY); +- this.cost.set(0); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit ++ this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item + return; + } + } +@@ -260,14 +280,14 @@ + } + + if (b0 == i && b0 > 0) { +- if (this.cost.get() >= 40) { +- this.cost.set(39); ++ if (this.cost.get() >= this.maximumRepairCost) { // CraftBukkit ++ this.cost.set(this.maximumRepairCost - 1); // CraftBukkit + } + + this.onlyRenaming = true; + } + +- if (this.cost.get() >= 40 && !this.player.getAbilities().instabuild) { ++ if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit + itemstack1 = ItemStack.EMPTY; + } + +@@ -285,12 +305,13 @@ + EnchantmentHelper.setEnchantments(itemstack1, itemenchantments_a.toImmutable()); + } + +- this.resultSlots.setItem(0, itemstack1); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit + this.broadcastChanges(); + } else { +- this.resultSlots.setItem(0, ItemStack.EMPTY); +- this.cost.set(0); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit ++ this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item + } ++ this.sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686, SPIGOT-7931: Always send completed inventory to stay in sync with client + } + + public static int calculateIncreasedRepairCost(int cost) { +@@ -313,6 +334,7 @@ + } + + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + return true; + } else { + return false; +@@ -329,4 +351,19 @@ + public int getCost() { + return this.cost.get(); + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftAnvilView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ org.bukkit.craftbukkit.inventory.CraftInventoryAnvil inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryAnvil( ++ this.access.getLocation(), this.inputSlots, this.resultSlots); ++ this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this); ++ this.bukkitEntity.updateFromLegacy(inventory); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch new file mode 100644 index 0000000000..587f354b49 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch @@ -0,0 +1,109 @@ +--- a/net/minecraft/world/inventory/BeaconMenu.java ++++ b/net/minecraft/world/inventory/BeaconMenu.java +@@ -8,10 +8,13 @@ + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.effect.MobEffect; ++import net.minecraft.world.entity.player.Inventory; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; ++import org.bukkit.craftbukkit.inventory.view.CraftBeaconView; ++// CraftBukkit end + + public class BeaconMenu extends AbstractContainerMenu { + +@@ -27,6 +30,10 @@ + private final BeaconMenu.PaymentSlot paymentSlot; + private final ContainerLevelAccess access; + private final ContainerData beaconData; ++ // CraftBukkit start ++ private CraftBeaconView bukkitEntity = null; ++ private Inventory player; ++ // CraftBukkit end + + public BeaconMenu(int syncId, Container inventory) { + this(syncId, inventory, new SimpleContainerData(3), ContainerLevelAccess.NULL); +@@ -34,7 +41,8 @@ + + public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) { + super(MenuType.BEACON, syncId); +- this.beacon = new SimpleContainer(this, 1) { ++ this.player = (Inventory) inventory; // CraftBukkit - TODO: check this ++ this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // CraftBukkit - decompile error // Paper - Add missing InventoryHolders + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + return stack.is(ItemTags.BEACON_PAYMENT_ITEMS); +@@ -44,6 +52,12 @@ + public int getMaxStackSize() { + return 1; + } ++ // Paper start - Fix inventories returning null Locations ++ @Override ++ public org.bukkit.Location getLocation() { ++ return context.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations + }; + checkContainerDataCount(propertyDelegate, 3); + this.beaconData = propertyDelegate; +@@ -69,6 +83,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.BEACON); + } + +@@ -148,12 +163,30 @@ + return BeaconMenu.decodeEffect(this.beaconData.get(2)); + } + ++ // Paper start - Add PlayerChangeBeaconEffectEvent ++ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional> optionalEffect) { ++ return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null); ++ } ++ // Paper end - Add PlayerChangeBeaconEffectEvent ++ + public void updateEffects(Optional> primary, Optional> secondary) { ++ // Paper start - fix MC-174630 - validate secondary power ++ if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) { ++ secondary = Optional.empty(); ++ } ++ // Paper end + if (this.paymentSlot.hasItem()) { +- this.beaconData.set(1, BeaconMenu.encodeEffect((Holder) primary.orElse((Object) null))); +- this.beaconData.set(2, BeaconMenu.encodeEffect((Holder) secondary.orElse((Object) null))); ++ // Paper start - Add PlayerChangeBeaconEffectEvent ++ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); ++ if (event.callEvent()) { ++ // Paper end - Add PlayerChangeBeaconEffectEvent ++ this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error ++ this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error ++ if (event.willConsumeItem()) { // Paper + this.paymentSlot.remove(1); ++ } // Paper + this.access.execute(Level::blockEntityChanged); ++ } // Paper end - Add PlayerChangeBeaconEffectEvent + } + + } +@@ -178,4 +211,17 @@ + return 1; + } + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftBeaconView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ org.bukkit.craftbukkit.inventory.CraftInventoryBeacon inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon(this.beacon); ++ this.bukkitEntity = new CraftBeaconView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch new file mode 100644 index 0000000000..36dcdb7277 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch @@ -0,0 +1,127 @@ +--- a/net/minecraft/world/inventory/BrewingStandMenu.java ++++ b/net/minecraft/world/inventory/BrewingStandMenu.java +@@ -16,6 +16,10 @@ + import net.minecraft.world.item.alchemy.Potion; + import net.minecraft.world.item.alchemy.PotionBrewing; + import net.minecraft.world.item.alchemy.PotionContents; ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventoryBrewer; ++import org.bukkit.craftbukkit.inventory.view.CraftBrewingStandView; ++// CraftBukkit end + + public class BrewingStandMenu extends AbstractContainerMenu { + +@@ -35,29 +39,51 @@ + public final ContainerData brewingStandData; + private final Slot ingredientSlot; + ++ // CraftBukkit start ++ private CraftBrewingStandView bukkitEntity = null; ++ private Inventory player; ++ // CraftBukkit end ++ + public BrewingStandMenu(int syncId, Inventory playerInventory) { +- this(syncId, playerInventory, new SimpleContainer(5), new SimpleContainerData(2)); ++ this(syncId, playerInventory, new SimpleContainer(5), new io.papermc.paper.inventory.BrewingSimpleContainerData()); // Paper - Add totalBrewTime + } + + public BrewingStandMenu(int syncId, Inventory playerInventory, Container inventory, ContainerData propertyDelegate) { + super(MenuType.BREWING_STAND, syncId); ++ this.player = playerInventory; // CraftBukkit + checkContainerSize(inventory, 5); +- checkContainerDataCount(propertyDelegate, 2); ++ checkContainerDataCount(propertyDelegate, 3); // Paper - Add recipeBrewTime + this.brewingStand = inventory; + this.brewingStandData = propertyDelegate; + PotionBrewing potionbrewer = playerInventory.player.level().potionBrewing(); + +- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51)); +- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58)); +- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51)); ++ // Paper start - custom potion mixes ++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51, potionbrewer)); ++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58, potionbrewer)); ++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51, potionbrewer)); ++ // Paper end - custom potion mixes + this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionbrewer, inventory, 3, 79, 17)); + this.addSlot(new BrewingStandMenu.FuelSlot(inventory, 4, 17, 17)); +- this.addDataSlots(propertyDelegate); ++ // Paper start - Add recipeBrewTime ++ this.addDataSlots(new SimpleContainerData(2) { ++ @Override ++ public int get(final int index) { ++ if (index == 0) return 400 * propertyDelegate.get(index) / propertyDelegate.get(2); ++ return propertyDelegate.get(index); ++ } ++ ++ @Override ++ public void set(final int index, final int value) { ++ propertyDelegate.set(index, value); ++ } ++ }); ++ // Paper end - Add recipeBrewTime + this.addStandardInventorySlots(playerInventory, 8, 84); + } + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.brewingStand.stillValid(player); + } + +@@ -79,7 +105,7 @@ + if (!this.moveItemStackTo(itemstack1, 3, 4, false)) { + return ItemStack.EMPTY; + } +- } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack)) { ++ } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes + if (!this.moveItemStackTo(itemstack1, 0, 3, false)) { + return ItemStack.EMPTY; + } +@@ -128,13 +154,15 @@ + + private static class PotionSlot extends Slot { + +- public PotionSlot(Container inventory, int index, int x, int y) { ++ private final PotionBrewing potionBrewing; // Paper - custom potion mixes ++ public PotionSlot(Container inventory, int index, int x, int y, PotionBrewing potionBrewing) { // Paper - custom potion mixes + super(inventory, index, x, y); ++ this.potionBrewing = potionBrewing; // Paper - custom potion mixes + } + + @Override + public boolean mayPlace(ItemStack stack) { +- return PotionSlot.mayPlaceItem(stack); ++ return PotionSlot.mayPlaceItem(stack, this.potionBrewing); // Paper - custom potion mixes + } + + @Override +@@ -153,8 +181,8 @@ + super.onTake(player, stack); + } + +- public static boolean mayPlaceItem(ItemStack stack) { +- return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE); ++ public static boolean mayPlaceItem(ItemStack stack, PotionBrewing potionBrewing) { // Paper - custom potion mixes ++ return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack); // Paper - Custom Potion Mixes + } + + @Override +@@ -198,4 +226,17 @@ + return BrewingStandMenu.EMPTY_SLOT_FUEL; + } + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftBrewingStandView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryBrewer inventory = new CraftInventoryBrewer(this.brewingStand); ++ this.bukkitEntity = new CraftBrewingStandView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch new file mode 100644 index 0000000000..b46cd6b42c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch @@ -0,0 +1,146 @@ +--- a/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -6,16 +6,36 @@ + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.item.MapItem; + import net.minecraft.world.item.component.MapPostProcessing; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.saveddata.maps.MapItemSavedData; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.inventory.CraftInventoryCartography; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.entity.Player; ++// CraftBukkit end + + public class CartographyTableMenu extends AbstractContainerMenu { + ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ private Player player; ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryCartography inventory = new CraftInventoryCartography(this.container, this.resultContainer); ++ this.bukkitEntity = new CraftInventoryView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + public static final int MAP_SLOT = 0; + public static final int ADDITIONAL_SLOT = 1; + public static final int RESULT_SLOT = 2; +@@ -34,28 +54,42 @@ + + public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) { + super(MenuType.CARTOGRAPHY_TABLE, syncId); +- this.container = new SimpleContainer(2) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + CartographyTableMenu.this.slotsChanged(this); + super.setChanged(); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; +- this.resultContainer = new ResultContainer() { ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { +- CartographyTableMenu.this.slotsChanged(this); ++ // CartographyTableMenu.this.slotsChanged(this); // Paper - Add CatographyItemEvent - do not recompute results if the result slot changes - allows to set the result slot via api + super.setChanged(); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; + this.access = context; +- this.addSlot(new Slot(this, this.container, 0, 15, 15) { ++ this.addSlot(new Slot(this.container, 0, 15, 15) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.has(DataComponents.MAP_ID); + } + }); +- this.addSlot(new Slot(this, this.container, 1, 15, 52) { ++ this.addSlot(new Slot(this.container, 1, 15, 52) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.is(Items.PAPER) || stack.is(Items.MAP) || stack.is(Items.GLASS_PANE); +@@ -68,7 +102,7 @@ + } + + @Override +- public void onTake(Player player, ItemStack stack) { ++ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + ((Slot) CartographyTableMenu.this.slots.get(0)).remove(1); + ((Slot) CartographyTableMenu.this.slots.get(1)).remove(1); + stack.getItem().onCraftedBy(stack, player.level(), player); +@@ -76,7 +110,7 @@ + long j = world.getGameTime(); + + if (CartographyTableMenu.this.lastSoundTime != j) { +- world.playSound((Player) null, blockposition, SoundEvents.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); ++ world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); + CartographyTableMenu.this.lastSoundTime = j; + } + +@@ -85,10 +119,12 @@ + } + }); + this.addStandardInventorySlots(inventory, 8, 84); ++ this.player = (Player) inventory.player.getBukkitEntity(); // CraftBukkit + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.CARTOGRAPHY_TABLE); + } + +@@ -104,6 +140,7 @@ + this.setupResultSlot(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { +@@ -147,7 +184,7 @@ + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot1 = (Slot) this.slots.get(slot); + +@@ -199,7 +236,7 @@ + } + + @Override +- public void removed(Player player) { ++ public void removed(net.minecraft.world.entity.player.Player player) { + super.removed(player); + this.resultContainer.removeItemNoUpdate(2); + this.access.execute((world, blockposition) -> { diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch new file mode 100644 index 0000000000..8ba0e1a6a1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch @@ -0,0 +1,64 @@ +--- a/net/minecraft/world/inventory/ChestMenu.java ++++ b/net/minecraft/world/inventory/ChestMenu.java +@@ -1,16 +1,43 @@ + package net.minecraft.world.inventory; + ++import net.minecraft.world.CompoundContainer; + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.player.Inventory; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end + + public class ChestMenu extends AbstractContainerMenu { + + private final Container container; + private final int containerRows; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ private Inventory player; + ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventory inventory; ++ if (this.container instanceof Inventory) { ++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryPlayer((Inventory) this.container); ++ } else if (this.container instanceof CompoundContainer) { ++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) this.container); ++ } else { ++ inventory = new CraftInventory(this.container); ++ } ++ ++ this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + private ChestMenu(MenuType type, int syncId, Inventory playerInventory, int rows) { + this(type, syncId, playerInventory, new SimpleContainer(9 * rows), rows); + } +@@ -53,6 +80,9 @@ + this.container = inventory; + this.containerRows = rows; + inventory.startOpen(playerInventory.player); ++ // CraftBukkit start - Save player ++ this.player = playerInventory; ++ // CraftBukkit end + boolean flag = true; + + this.addChestGrid(inventory, 8, 18); +@@ -72,6 +102,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.container.stillValid(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch new file mode 100644 index 0000000000..3202223f24 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/world/inventory/ContainerLevelAccess.java ++++ b/net/minecraft/world/inventory/ContainerLevelAccess.java +@@ -8,16 +8,66 @@ + + public interface ContainerLevelAccess { + ++ // CraftBukkit start ++ default Level getWorld() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ default BlockPos getPosition() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ default org.bukkit.Location getLocation() { ++ return new org.bukkit.Location(this.getWorld().getWorld(), this.getPosition().getX(), this.getPosition().getY(), this.getPosition().getZ()); ++ } ++ // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ default boolean isBlock() { ++ return false; ++ } ++ ++ default org.bukkit.inventory.@org.jetbrains.annotations.Nullable BlockInventoryHolder createBlockHolder(AbstractContainerMenu menu) { ++ if (!this.isBlock()) { ++ return null; ++ } ++ return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory()); ++ } ++ // Paper end - Add missing InventoryHolders ++ + ContainerLevelAccess NULL = new ContainerLevelAccess() { + @Override + public Optional evaluate(BiFunction getter) { + return Optional.empty(); + } ++ // Paper start - fix menus with empty level accesses ++ @Override ++ public org.bukkit.Location getLocation() { ++ return null; ++ } ++ // Paper end - fix menus with empty level accesses + }; + + static ContainerLevelAccess create(final Level world, final BlockPos pos) { + return new ContainerLevelAccess() { ++ // CraftBukkit start + @Override ++ public Level getWorld() { ++ return world; ++ } ++ ++ @Override ++ public BlockPos getPosition() { ++ return pos; ++ } ++ // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ @Override ++ public boolean isBlock() { ++ return true; ++ } ++ // Paper end - Add missing InventoryHolders ++ ++ @Override + public Optional evaluate(BiFunction getter) { + return Optional.of(getter.apply(world, pos)); + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch new file mode 100644 index 0000000000..7d9e714dfe --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/inventory/ContainerListener.java ++++ b/net/minecraft/world/inventory/ContainerListener.java +@@ -5,5 +5,11 @@ + public interface ContainerListener { + void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack); + ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ slotChanged(handler, slotId, stack); ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent ++ + void dataChanged(AbstractContainerMenu handler, int property, int value); + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch new file mode 100644 index 0000000000..a76ea56e99 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/inventory/ContainerSynchronizer.java ++++ b/net/minecraft/world/inventory/ContainerSynchronizer.java +@@ -6,6 +6,7 @@ + public interface ContainerSynchronizer { + void sendInitialData(AbstractContainerMenu handler, NonNullList stacks, ItemStack cursorStack, int[] properties); + ++ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus + void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack); + + void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch new file mode 100644 index 0000000000..e998f9ae45 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/inventory/CrafterMenu.java ++++ b/net/minecraft/world/inventory/CrafterMenu.java +@@ -10,8 +10,27 @@ + import net.minecraft.world.item.crafting.CraftingRecipe; + import net.minecraft.world.level.block.CrafterBlock; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafter; ++import org.bukkit.craftbukkit.inventory.view.CraftCrafterView; ++// CraftBukkit end ++ + public class CrafterMenu extends AbstractContainerMenu implements ContainerListener { + ++ // CraftBukkit start ++ private CraftCrafterView bukkitEntity = null; ++ ++ @Override ++ public CraftCrafterView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryCrafter inventory = new CraftInventoryCrafter(this.container, this.resultContainer); ++ this.bukkitEntity = new CraftCrafterView(this.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + protected static final int SLOT_COUNT = 9; + private static final int INV_SLOT_START = 9; + private static final int INV_SLOT_END = 36; +@@ -106,6 +125,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.container.stillValid(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch new file mode 100644 index 0000000000..0c5babe735 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/inventory/CraftingContainer.java ++++ b/net/minecraft/world/inventory/CraftingContainer.java +@@ -5,6 +5,10 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.CraftingInput; + ++// CraftBukkit start ++import net.minecraft.world.item.crafting.RecipeHolder; ++// CraftBukkit end ++ + public interface CraftingContainer extends Container, StackedContentsCompatible { + + int getWidth(); +@@ -13,6 +17,15 @@ + + List getItems(); + ++ // CraftBukkit start ++ default RecipeHolder getCurrentRecipe() { ++ return null; ++ } ++ ++ default void setCurrentRecipe(RecipeHolder recipe) { ++ } ++ // CraftBukkit end ++ + default CraftingInput asCraftInput() { + return this.asPositionedCraftInput().input(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch new file mode 100644 index 0000000000..9f0941d40b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/inventory/CraftingMenu.java ++++ b/net/minecraft/world/inventory/CraftingMenu.java +@@ -14,7 +14,11 @@ + import net.minecraft.world.item.crafting.CraftingRecipe; + import net.minecraft.world.item.crafting.RecipeHolder; + import net.minecraft.world.item.crafting.RecipeType; ++import net.minecraft.world.item.crafting.RepairItemRecipe; + import net.minecraft.world.level.block.Blocks; ++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end + + public class CraftingMenu extends AbstractCraftingMenu { + +@@ -31,13 +35,16 @@ + public final ContainerLevelAccess access; + private final Player player; + private boolean placingRecipe; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ // CraftBukkit end + + public CraftingMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, ContainerLevelAccess.NULL); + } + + public CraftingMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { +- super(MenuType.CRAFTING, syncId, 3, 3); ++ super(MenuType.CRAFTING, syncId, 3, 3, playerInventory); // CraftBukkit - pass player + this.access = context; + this.player = playerInventory.player; + this.addResultSlot(this.player, 124, 35); +@@ -50,6 +57,7 @@ + ServerPlayer entityplayer = (ServerPlayer) player; + ItemStack itemstack = ItemStack.EMPTY; + Optional> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe); ++ craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit + + if (optional.isPresent()) { + RecipeHolder recipeholder1 = (RecipeHolder) optional.get(); +@@ -63,6 +71,7 @@ + } + } + } ++ itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(craftingInventory, resultInventory, itemstack, handler.getBukkitView(), optional.map(RecipeHolder::value).orElse(null) instanceof RepairItemRecipe); // CraftBukkit + + resultInventory.setItem(0, itemstack); + handler.setRemoteSlot(0, itemstack); +@@ -103,6 +112,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.CRAFTING_TABLE); + } + +@@ -181,4 +191,17 @@ + protected Player owner() { + return this.player; + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots); ++ this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch new file mode 100644 index 0000000000..18ed7c2049 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch @@ -0,0 +1,62 @@ +--- a/net/minecraft/world/inventory/DispenserMenu.java ++++ b/net/minecraft/world/inventory/DispenserMenu.java +@@ -6,6 +6,11 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end ++ + public class DispenserMenu extends AbstractContainerMenu { + + private static final int SLOT_COUNT = 9; +@@ -14,6 +19,10 @@ + private static final int USE_ROW_SLOT_START = 36; + private static final int USE_ROW_SLOT_END = 45; + public final Container dispenser; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ private Inventory player; ++ // CraftBukkit end + + public DispenserMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainer(9)); +@@ -21,6 +30,10 @@ + + public DispenserMenu(int syncId, Inventory playerInventory, Container inventory) { + super(MenuType.GENERIC_3x3, syncId); ++ // CraftBukkit start - Save player ++ this.player = playerInventory; ++ // CraftBukkit end ++ + checkContainerSize(inventory, 9); + this.dispenser = inventory; + inventory.startOpen(playerInventory.player); +@@ -41,6 +54,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.dispenser.stillValid(player); + } + +@@ -82,4 +96,17 @@ + super.removed(player); + this.dispenser.stopOpen(player); + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventory inventory = new CraftInventory(this.dispenser); ++ this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch new file mode 100644 index 0000000000..531e6792cc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch @@ -0,0 +1,270 @@ +--- a/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -21,7 +21,6 @@ + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.item.enchantment.Enchantment; +@@ -29,6 +28,18 @@ + import net.minecraft.world.item.enchantment.EnchantmentInstance; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.EnchantingTableBlock; ++// CraftBukkit start ++import java.util.Map; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.enchantments.CraftEnchantment; ++import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView; ++import org.bukkit.enchantments.EnchantmentOffer; ++import org.bukkit.event.enchantment.EnchantItemEvent; ++import org.bukkit.event.enchantment.PrepareItemEnchantEvent; ++import org.bukkit.entity.Player; ++// CraftBukkit end + + public class EnchantmentMenu extends AbstractContainerMenu { + +@@ -40,6 +51,10 @@ + public final int[] costs; + public final int[] enchantClue; + public final int[] levelClue; ++ // CraftBukkit start ++ private CraftEnchantmentView bukkitEntity = null; ++ private Player player; ++ // CraftBukkit end + + public EnchantmentMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, ContainerLevelAccess.NULL); +@@ -47,12 +62,19 @@ + + public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { + super(MenuType.ENCHANTMENT, syncId); +- this.enchantSlots = new SimpleContainer(2) { ++ this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); + EnchantmentMenu.this.slotsChanged(this); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; + this.random = RandomSource.create(); + this.enchantmentSeed = DataSlot.standalone(); +@@ -60,13 +82,13 @@ + this.enchantClue = new int[]{-1, -1, -1}; + this.levelClue = new int[]{-1, -1, -1}; + this.access = context; +- this.addSlot(new Slot(this, this.enchantSlots, 0, 15, 47) { ++ this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) { // CraftBukkit - decompile error + @Override + public int getMaxStackSize() { + return 1; + } + }); +- this.addSlot(new Slot(this, this.enchantSlots, 1, 35, 47) { ++ this.addSlot(new Slot(this.enchantSlots, 1, 35, 47) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.is(Items.LAPIS_LAZULI); +@@ -88,6 +110,9 @@ + this.addDataSlot(DataSlot.shared(this.levelClue, 0)); + this.addDataSlot(DataSlot.shared(this.levelClue, 1)); + this.addDataSlot(DataSlot.shared(this.levelClue, 2)); ++ // CraftBukkit start ++ this.player = (Player) playerInventory.player.getBukkitEntity(); ++ // CraftBukkit end + } + + @Override +@@ -95,7 +120,7 @@ + if (inventory == this.enchantSlots) { + ItemStack itemstack = inventory.getItem(0); + +- if (!itemstack.isEmpty() && itemstack.isEnchantable()) { ++ if (!itemstack.isEmpty()) { // CraftBukkit - relax condition + this.access.execute((world, blockposition) -> { + IdMap> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + int i = 0; +@@ -135,6 +160,41 @@ + } + } + ++ // CraftBukkit start ++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack); ++ org.bukkit.enchantments.EnchantmentOffer[] offers = new EnchantmentOffer[3]; ++ for (j = 0; j < 3; ++j) { ++ org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[j])) : null; ++ offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null; ++ } ++ ++ PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(this.player, this.getBukkitView(), this.access.getLocation().getBlock(), item, offers, i); ++ event.setCancelled(!itemstack.isEnchantable()); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ for (j = 0; j < 3; ++j) { ++ this.costs[j] = 0; ++ this.enchantClue[j] = -1; ++ this.levelClue[j] = -1; ++ } ++ return; ++ } ++ ++ for (j = 0; j < 3; j++) { ++ EnchantmentOffer offer = event.getOffers()[j]; ++ if (offer != null) { ++ this.costs[j] = offer.getCost(); ++ this.enchantClue[j] = registry.getId(CraftEnchantment.bukkitToMinecraftHolder(offer.getEnchantment())); ++ this.levelClue[j] = offer.getEnchantmentLevel(); ++ } else { ++ this.costs[j] = 0; ++ this.enchantClue[j] = -1; ++ this.levelClue[j] = -1; ++ } ++ } ++ // CraftBukkit end ++ + this.broadcastChanges(); + }); + } else { +@@ -149,7 +209,7 @@ + } + + @Override +- public boolean clickMenuButton(Player player, int id) { ++ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (id >= 0 && id < this.costs.length) { + ItemStack itemstack = this.enchantSlots.getItem(0); + ItemStack itemstack1 = this.enchantSlots.getItem(1); +@@ -159,24 +219,55 @@ + return false; + } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) { + this.access.execute((world, blockposition) -> { +- ItemStack itemstack2 = itemstack; ++ ItemStack itemstack2 = itemstack; // Paper - diff on change + List list = this.getEnchantmentList(world.registryAccess(), itemstack, id, this.costs[id]); + +- if (!list.isEmpty()) { +- player.onEnchantmentPerformed(itemstack, j); +- if (itemstack.is(Items.BOOK)) { +- itemstack2 = itemstack.transmuteCopy(Items.ENCHANTED_BOOK); +- this.enchantSlots.setItem(0, itemstack2); ++ // CraftBukkit start ++ IdMap> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); ++ if (true || !list.isEmpty()) { ++ // entityhuman.onEnchantmentPerformed(itemstack, j); // Moved down ++ Map enchants = new java.util.HashMap(); ++ for (EnchantmentInstance instance : list) { ++ enchants.put(CraftEnchantment.minecraftHolderToBukkit(instance.enchantment), instance.level); + } ++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack2); + +- Iterator iterator = list.iterator(); ++ org.bukkit.enchantments.Enchantment hintedEnchantment = CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[id])); ++ int hintedEnchantmentLevel = this.levelClue[id]; ++ EnchantItemEvent event = new EnchantItemEvent((Player) player.getBukkitEntity(), this.getBukkitView(), this.access.getLocation().getBlock(), item, this.costs[id], enchants, hintedEnchantment, hintedEnchantmentLevel, id); ++ world.getCraftServer().getPluginManager().callEvent(event); + +- while (iterator.hasNext()) { +- EnchantmentInstance weightedrandomenchant = (EnchantmentInstance) iterator.next(); ++ int level = event.getExpLevelCost(); ++ if (event.isCancelled() || (level > player.experienceLevel && !player.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) { ++ return; ++ } ++ // CraftBukkit end ++ // Paper start ++ itemstack2 = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(item, event.getItem()); ++ if (itemstack2 != itemstack) { ++ this.enchantSlots.setItem(0, itemstack2); ++ } ++ if (itemstack2.is(Items.BOOK)) { ++ itemstack2 = itemstack2.transmuteCopy(Items.ENCHANTED_BOOK); ++ this.enchantSlots.setItem(0, itemstack2); ++ } ++ // Paper end + ++ // CraftBukkit start ++ for (Map.Entry entry : event.getEnchantsToAdd().entrySet()) { ++ Holder nms = CraftEnchantment.bukkitToMinecraftHolder(entry.getKey()); ++ if (nms == null) { ++ continue; ++ } ++ ++ EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue()); + itemstack2.enchant(weightedrandomenchant.enchantment, weightedrandomenchant.level); + } + ++ player.onEnchantmentPerformed(itemstack, j); ++ // CraftBukkit end ++ ++ // CraftBukkit - TODO: let plugins change this + itemstack1.consume(j, player); + if (itemstack1.isEmpty()) { + this.enchantSlots.setItem(1, ItemStack.EMPTY); +@@ -190,7 +281,7 @@ + this.enchantSlots.setChanged(); + this.enchantmentSeed.set(player.getEnchantmentSeed()); + this.slotsChanged(this.enchantSlots); +- world.playSound((Player) null, blockposition, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1.0F, world.random.nextFloat() * 0.1F + 0.9F); ++ world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1.0F, world.random.nextFloat() * 0.1F + 0.9F); + } + + }); +@@ -234,7 +325,7 @@ + } + + @Override +- public void removed(Player player) { ++ public void removed(net.minecraft.world.entity.player.Player player) { + super.removed(player); + this.access.execute((world, blockposition) -> { + this.clearContainer(player, this.enchantSlots); +@@ -242,12 +333,13 @@ + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.ENCHANTING_TABLE); + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot1 = (Slot) this.slots.get(slot); + +@@ -293,4 +385,23 @@ + + return itemstack; + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftEnchantmentView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots); ++ this.bukkitEntity = new CraftEnchantmentView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ ++ // Paper start - add enchantment seed update API ++ public void setEnchantmentSeed(int seed) { ++ this.enchantmentSeed.set(seed); ++ } ++ // Paper end - add enchantment seed update API + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch new file mode 100644 index 0000000000..488614cbf6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/inventory/FurnaceResultSlot.java ++++ b/net/minecraft/world/inventory/FurnaceResultSlot.java +@@ -51,7 +51,7 @@ + Container iinventory = this.container; + + if (iinventory instanceof AbstractFurnaceBlockEntity tileentityfurnace) { +- tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer); ++ tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer, stack, this.removeCount); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch new file mode 100644 index 0000000000..0ba3910b94 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch @@ -0,0 +1,141 @@ +--- a/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -10,7 +10,6 @@ + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + import net.minecraft.world.item.enchantment.Enchantment; +@@ -19,9 +18,30 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.inventory.CraftInventoryGrindstone; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.entity.Player; ++// CraftBukkit end + + public class GrindstoneMenu extends AbstractContainerMenu { + ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ private Player player; ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.repairSlots, this.resultSlots); ++ this.bukkitEntity = new CraftInventoryView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + public static final int MAX_NAME_LENGTH = 35; + public static final int INPUT_SLOT = 0; + public static final int ADDITIONAL_SLOT = 1; +@@ -40,22 +60,29 @@ + + public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.GRINDSTONE, syncId); +- this.resultSlots = new ResultContainer(); +- this.repairSlots = new SimpleContainer(2) { ++ this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders ++ this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); + GrindstoneMenu.this.slotsChanged(this); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; + this.access = context; +- this.addSlot(new Slot(this, this.repairSlots, 0, 49, 19) { ++ this.addSlot(new Slot(this.repairSlots, 0, 49, 19) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.isDamageableItem() || EnchantmentHelper.hasAnyEnchantments(stack); + } + }); +- this.addSlot(new Slot(this, this.repairSlots, 1, 49, 40) { ++ this.addSlot(new Slot(this.repairSlots, 1, 49, 40) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.isDamageableItem() || EnchantmentHelper.hasAnyEnchantments(stack); +@@ -68,10 +95,14 @@ + } + + @Override +- public void onTake(Player player, ItemStack stack) { ++ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + context.execute((world, blockposition) -> { + if (world instanceof ServerLevel) { +- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world)); ++ // Paper start - Fire BlockExpEvent on grindstone use ++ org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition), this.getExperienceAmount(world)); ++ event.callEvent(); ++ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); ++ // Paper end - Fire BlockExpEvent on grindstone use + } + + world.levelEvent(1042, blockposition, 0); +@@ -113,6 +144,7 @@ + } + }); + this.addStandardInventorySlots(playerInventory, 8, 84); ++ this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit + } + + @Override +@@ -120,12 +152,14 @@ + super.slotsChanged(inventory); + if (inventory == this.repairSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + } + + private void createResult() { +- this.resultSlots.setItem(0, this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1))); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(this.getBukkitView(), this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1))); // CraftBukkit ++ this.sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client + this.broadcastChanges(); + } + +@@ -218,7 +252,7 @@ + } + + @Override +- public void removed(Player player) { ++ public void removed(net.minecraft.world.entity.player.Player player) { + super.removed(player); + this.access.execute((world, blockposition) -> { + this.clearContainer(player, this.repairSlots); +@@ -226,12 +260,13 @@ + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.GRINDSTONE); + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot1 = (Slot) this.slots.get(slot); + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch new file mode 100644 index 0000000000..2174c38e81 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/inventory/HopperMenu.java ++++ b/net/minecraft/world/inventory/HopperMenu.java +@@ -6,11 +6,32 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end ++ + public class HopperMenu extends AbstractContainerMenu { + + public static final int CONTAINER_SIZE = 5; + private final Container hopper; + ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ private Inventory player; ++ ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventory inventory = new CraftInventory(this.hopper); ++ this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + public HopperMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainer(5)); + } +@@ -18,6 +39,7 @@ + public HopperMenu(int syncId, Inventory playerInventory, Container inventory) { + super(MenuType.HOPPER, syncId); + this.hopper = inventory; ++ this.player = playerInventory; // CraftBukkit - save player + checkContainerSize(inventory, 5); + inventory.startOpen(playerInventory.player); + +@@ -30,6 +52,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.hopper.stillValid(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch new file mode 100644 index 0000000000..2657cf4ccb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/inventory/HorseInventoryMenu.java ++++ b/net/minecraft/world/inventory/HorseInventoryMenu.java +@@ -11,6 +11,11 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++import org.bukkit.inventory.InventoryView; ++// CraftBukkit end ++ + public class HorseInventoryMenu extends AbstractContainerMenu { + + static final ResourceLocation SADDLE_SLOT_SPRITE = ResourceLocation.withDefaultNamespace("container/slot/saddle"); +@@ -22,13 +27,28 @@ + public static final int SLOT_BODY_ARMOR = 1; + private static final int SLOT_HORSE_INVENTORY_START = 2; + ++ // CraftBukkit start ++ org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity; ++ Inventory player; ++ ++ @Override ++ public InventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ return this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), this.horseContainer.getOwner().getInventory(), this); ++ } ++ + public HorseInventoryMenu(int syncId, Inventory playerInventory, Container inventory, final AbstractHorse entity, int slotColumnCount) { + super((MenuType) null, syncId); ++ this.player = playerInventory; ++ // CraftBukkit end + this.horseContainer = inventory; + this.armorContainer = entity.getBodyArmorAccess(); + this.horse = entity; + inventory.startOpen(playerInventory.player); +- this.addSlot(new Slot(this, inventory, 0, 8, 18) { ++ this.addSlot(new Slot(inventory, 0, 8, 18) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.is(Items.SADDLE) && !this.hasItem() && entity.isSaddleable(); +@@ -46,7 +66,7 @@ + }); + ResourceLocation minecraftkey = entity instanceof Llama ? HorseInventoryMenu.LLAMA_ARMOR_SLOT_SPRITE : HorseInventoryMenu.ARMOR_SLOT_SPRITE; + +- this.addSlot(new ArmorSlot(this, this.armorContainer, entity, EquipmentSlot.BODY, 0, 8, 36, minecraftkey) { ++ this.addSlot(new ArmorSlot(this.armorContainer, entity, EquipmentSlot.BODY, 0, 8, 36, minecraftkey) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return entity.isEquippableInSlot(stack, EquipmentSlot.BODY); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch new file mode 100644 index 0000000000..78a5d31f7d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch @@ -0,0 +1,64 @@ +--- a/net/minecraft/world/inventory/InventoryMenu.java ++++ b/net/minecraft/world/inventory/InventoryMenu.java +@@ -2,6 +2,7 @@ + + import java.util.List; + import java.util.Map; ++import net.minecraft.network.chat.Component; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.Container; +@@ -11,6 +12,9 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.RecipeHolder; + import net.minecraft.world.level.Level; ++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end + + public class InventoryMenu extends AbstractCraftingMenu { + +@@ -38,9 +42,15 @@ + private static final EquipmentSlot[] SLOT_IDS = new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + public final boolean active; + private final Player owner; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity = null; ++ // CraftBukkit end + + public InventoryMenu(Inventory inventory, boolean onServer, final Player owner) { +- super((MenuType) null, 0, 2, 2); ++ // CraftBukkit start ++ super((MenuType) null, 0, 2, 2, inventory); // CraftBukkit - save player ++ this.setTitle(Component.translatable("container.crafting")); // SPIGOT-4722: Allocate title for player inventory ++ // CraftBukkit end + this.active = onServer; + this.owner = owner; + this.addResultSlot(owner, 154, 28); +@@ -54,7 +64,7 @@ + } + + this.addStandardInventorySlots(inventory, 8, 84); +- this.addSlot(new Slot(this, inventory, 40, 77, 62) { ++ this.addSlot(new Slot(inventory, 40, 77, 62) { // CraftBukkit - decompile error + @Override + public void setByPlayer(ItemStack stack, ItemStack previousStack) { + owner.onEquipItem(EquipmentSlot.OFFHAND, previousStack, stack); +@@ -190,4 +200,17 @@ + protected Player owner() { + return this.owner; + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots); ++ this.bukkitEntity = new CraftInventoryView(this.owner.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch new file mode 100644 index 0000000000..7d50fbdafb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch @@ -0,0 +1,65 @@ +--- a/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -17,12 +17,7 @@ + protected final ContainerLevelAccess access; + protected final Player player; + protected final Container inputSlots; +- protected final ResultContainer resultSlots = new ResultContainer() { +- @Override +- public void setChanged() { +- ItemCombinerMenu.this.slotsChanged(this); +- } +- }; ++ protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init + private final int resultSlotIndex; + + protected boolean mayPickup(Player player, boolean present) { +@@ -36,6 +31,14 @@ + public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context, ItemCombinerMenuSlotDefinition forgingSlotsManager) { + super(type, syncId); + this.access = context; ++ // Paper start - Add missing InventoryHolders; delay field init ++ this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)) { ++ @Override ++ public void setChanged() { ++ ItemCombinerMenu.this.slotsChanged(this); ++ } ++ }; ++ // Paper end - Add missing InventoryHolders; delay field init + this.player = playerInventory.player; + this.inputSlots = this.createContainer(forgingSlotsManager.getNumOfInputSlots()); + this.resultSlotIndex = forgingSlotsManager.getResultSlotIndex(); +@@ -50,7 +53,7 @@ + while (iterator.hasNext()) { + final ItemCombinerMenuSlotDefinition.SlotDefinition itemcombinermenuslotdefinition_b = (ItemCombinerMenuSlotDefinition.SlotDefinition) iterator.next(); + +- this.addSlot(new Slot(this, this.inputSlots, itemcombinermenuslotdefinition_b.slotIndex(), itemcombinermenuslotdefinition_b.x(), itemcombinermenuslotdefinition_b.y()) { ++ this.addSlot(new Slot(this.inputSlots, itemcombinermenuslotdefinition_b.slotIndex(), itemcombinermenuslotdefinition_b.x(), itemcombinermenuslotdefinition_b.y()) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return itemcombinermenuslotdefinition_b.mayPlace().test(stack); +@@ -82,7 +85,7 @@ + public abstract void createResult(); + + private SimpleContainer createContainer(int size) { +- return new SimpleContainer(size) { ++ return new SimpleContainer(this.createBlockHolder(this.access), size) { + @Override + public void setChanged() { + super.setChanged(); +@@ -96,6 +99,7 @@ + super.slotsChanged(inventory); + if (inventory == this.inputSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent + } + + } +@@ -110,6 +114,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return (Boolean) this.access.evaluate((world, blockposition) -> { + return !this.isValidBlock(world.getBlockState(blockposition)) ? false : player.canInteractWithBlock(blockposition, 4.0D); + }, true); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch new file mode 100644 index 0000000000..667c188455 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch @@ -0,0 +1,143 @@ +--- a/net/minecraft/world/inventory/LecternMenu.java ++++ b/net/minecraft/world/inventory/LecternMenu.java +@@ -2,11 +2,33 @@ + + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; +-import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.entity.player.Inventory; + import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.entity.LecternBlockEntity.LecternInventory; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.inventory.CraftInventoryLectern; ++import org.bukkit.craftbukkit.inventory.view.CraftLecternView; ++import org.bukkit.entity.Player; ++import org.bukkit.event.player.PlayerTakeLecternBookEvent; ++// CraftBukkit end + + public class LecternMenu extends AbstractContainerMenu { + ++ // CraftBukkit start ++ private CraftLecternView bukkitEntity = null; ++ private Player player; ++ ++ @Override ++ public CraftLecternView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryLectern inventory = new CraftInventoryLectern(this.lectern); ++ this.bukkitEntity = new CraftLecternView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + private static final int DATA_COUNT = 1; + private static final int SLOT_COUNT = 1; + public static final int BUTTON_PREV_PAGE = 1; +@@ -16,29 +38,33 @@ + private final Container lectern; + private final ContainerData lecternData; + +- public LecternMenu(int syncId) { +- this(syncId, new SimpleContainer(1), new SimpleContainerData(1)); ++ // CraftBukkit start - add player ++ public LecternMenu(int i, Inventory playerinventory) { ++ this(i, new SimpleContainer(1), new SimpleContainerData(1), playerinventory); + } + +- public LecternMenu(int syncId, Container inventory, ContainerData propertyDelegate) { +- super(MenuType.LECTERN, syncId); +- checkContainerSize(inventory, 1); +- checkContainerDataCount(propertyDelegate, 1); +- this.lectern = inventory; +- this.lecternData = propertyDelegate; +- this.addSlot(new Slot(inventory, 0, 0, 0) { ++ public LecternMenu(int i, Container iinventory, ContainerData icontainerproperties, Inventory playerinventory) { ++ // CraftBukkit end ++ super(MenuType.LECTERN, i); ++ checkContainerSize(iinventory, 1); ++ checkContainerDataCount(icontainerproperties, 1); ++ this.lectern = iinventory; ++ this.lecternData = icontainerproperties; ++ this.addSlot(new Slot(iinventory, 0, 0, 0) { + @Override + public void setChanged() { + super.setChanged(); + LecternMenu.this.slotsChanged(this.container); + } + }); +- this.addDataSlots(propertyDelegate); ++ this.addDataSlots(icontainerproperties); ++ this.player = (Player) playerinventory.player.getBukkitEntity(); // CraftBukkit + } + + @Override +- public boolean clickMenuButton(Player player, int id) { ++ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + int j; ++ io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper - Add PlayerLecternPageChangeEvent + + if (id >= 100) { + j = id - 100; +@@ -48,17 +74,38 @@ + switch (id) { + case 1: + j = this.lecternData.get(0); +- this.setData(0, j - 1); ++ // Paper start - Add PlayerLecternPageChangeEvent ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end - Add PlayerLecternPageChangeEvent + return true; + case 2: + j = this.lecternData.get(0); +- this.setData(0, j + 1); ++ // Paper start - Add PlayerLecternPageChangeEvent ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end - Add PlayerLecternPageChangeEvent + return true; + case 3: + if (!player.mayBuild()) { + return false; + } + ++ // CraftBukkit start - Event for taking the book ++ PlayerTakeLecternBookEvent event = new PlayerTakeLecternBookEvent(this.player, ((CraftInventoryLectern) this.getBukkitView().getTopInventory()).getHolder()); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + ItemStack itemstack = this.lectern.removeItemNoUpdate(0); + + this.lectern.setChanged(); +@@ -74,7 +121,7 @@ + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + return ItemStack.EMPTY; + } + +@@ -85,7 +132,9 @@ + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (this.lectern instanceof LecternInventory && !((LecternInventory) this.lectern).getLectern().hasBook()) return false; // CraftBukkit ++ if (!this.checkReachable) return true; // CraftBukkit + return this.lectern.stillValid(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch new file mode 100644 index 0000000000..1d534db5e0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch @@ -0,0 +1,203 @@ +--- a/net/minecraft/world/inventory/LoomMenu.java ++++ b/net/minecraft/world/inventory/LoomMenu.java +@@ -12,7 +12,6 @@ + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.BannerItem; + import net.minecraft.world.item.BannerPatternItem; + import net.minecraft.world.item.DyeColor; +@@ -22,9 +21,30 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.entity.BannerPattern; + import net.minecraft.world.level.block.entity.BannerPatternLayers; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.inventory.CraftInventoryLoom; ++import org.bukkit.craftbukkit.inventory.view.CraftLoomView; ++import org.bukkit.entity.Player; ++// CraftBukkit end + + public class LoomMenu extends AbstractContainerMenu { + ++ // CraftBukkit start ++ private CraftLoomView bukkitEntity = null; ++ private Player player; ++ ++ @Override ++ public CraftLoomView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryLoom inventory = new CraftInventoryLoom(this.inputContainer, this.outputContainer); ++ this.bukkitEntity = new CraftLoomView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + private static final int PATTERN_NOT_SET = -1; + private static final int INV_SLOT_START = 4; + private static final int INV_SLOT_END = 31; +@@ -53,35 +73,49 @@ + this.selectablePatterns = List.of(); + this.slotUpdateListener = () -> { + }; +- this.inputContainer = new SimpleContainer(3) { ++ this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); + LoomMenu.this.slotsChanged(this); + LoomMenu.this.slotUpdateListener.run(); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; +- this.outputContainer = new SimpleContainer(1) { ++ this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); + LoomMenu.this.slotUpdateListener.run(); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; + this.access = context; +- this.bannerSlot = this.addSlot(new Slot(this, this.inputContainer, 0, 13, 26) { ++ this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.getItem() instanceof BannerItem; + } + }); +- this.dyeSlot = this.addSlot(new Slot(this, this.inputContainer, 1, 33, 26) { ++ this.dyeSlot = this.addSlot(new Slot(this.inputContainer, 1, 33, 26) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.getItem() instanceof DyeItem; + } + }); +- this.patternSlot = this.addSlot(new Slot(this, this.inputContainer, 2, 23, 45) { ++ this.patternSlot = this.addSlot(new Slot(this.inputContainer, 2, 23, 45) { // CraftBukkit - decompile error + @Override + public boolean mayPlace(ItemStack stack) { + return stack.getItem() instanceof BannerPatternItem; +@@ -94,7 +128,7 @@ + } + + @Override +- public void onTake(Player player, ItemStack stack) { ++ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + LoomMenu.this.bannerSlot.remove(1); + LoomMenu.this.dyeSlot.remove(1); + if (!LoomMenu.this.bannerSlot.hasItem() || !LoomMenu.this.dyeSlot.hasItem()) { +@@ -105,7 +139,7 @@ + long j = world.getGameTime(); + + if (LoomMenu.this.lastSoundTime != j) { +- world.playSound((Player) null, blockposition, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); ++ world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); + LoomMenu.this.lastSoundTime = j; + } + +@@ -116,18 +150,44 @@ + this.addStandardInventorySlots(playerInventory, 8, 84); + this.addDataSlot(this.selectedBannerPatternIndex); + this.patternGetter = playerInventory.player.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN); ++ this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.LOOM); + } + + @Override +- public boolean clickMenuButton(Player player, int id) { ++ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (id >= 0 && id < this.selectablePatterns.size()) { +- this.selectedBannerPatternIndex.set(id); +- this.setupResultSlot((Holder) this.selectablePatterns.get(id)); ++ // Paper start - Add PlayerLoomPatternSelectEvent ++ int selectablePatternIndex = id; ++ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex))); ++ if (!event.callEvent()) { ++ player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ final Holder eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType()); ++ Holder selectedPattern = null; ++ for (int i = 0; i < this.selectablePatterns.size(); i++) { ++ final Holder holder = this.selectablePatterns.get(i); ++ if (eventPattern.equals(holder)) { ++ selectablePatternIndex = i; ++ selectedPattern = holder; ++ break; ++ } ++ } ++ if (selectedPattern == null) { ++ selectedPattern = eventPattern; ++ selectablePatternIndex = -1; ++ } ++ ++ player.containerMenu.sendAllDataToRemote(); ++ this.selectedBannerPatternIndex.set(selectablePatternIndex); ++ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected")); ++ // Paper end - Add PlayerLoomPatternSelectEvent + return true; + } else { + return false; +@@ -201,7 +261,8 @@ + this.resultSlot.set(ItemStack.EMPTY); + } + +- this.broadcastChanges(); ++ // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent + } else { + this.resultSlot.set(ItemStack.EMPTY); + this.selectablePatterns = List.of(); +@@ -222,7 +283,7 @@ + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot1 = (Slot) this.slots.get(slot); + +@@ -277,7 +338,7 @@ + } + + @Override +- public void removed(Player player) { ++ public void removed(net.minecraft.world.entity.player.Player player) { + super.removed(player); + this.access.execute((world, blockposition) -> { + this.clearContainer(player, this.inputContainer); +@@ -294,6 +355,11 @@ + DyeColor enumcolor = ((DyeItem) itemstack1.getItem()).getDyeColor(); + + itemstack2.update(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY, (bannerpatternlayers) -> { ++ // CraftBukkit start ++ if (bannerpatternlayers.layers().size() > 20) { ++ bannerpatternlayers = new BannerPatternLayers(List.copyOf(bannerpatternlayers.layers().subList(0, 20))); ++ } ++ // CraftBukkit end + return (new BannerPatternLayers.Builder()).addAll(bannerpatternlayers).add(pattern, enumcolor).build(); + }); + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch new file mode 100644 index 0000000000..ac3dc03175 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/inventory/MenuType.java ++++ b/net/minecraft/world/inventory/MenuType.java +@@ -28,7 +28,7 @@ + public static final MenuType GRINDSTONE = MenuType.register("grindstone", GrindstoneMenu::new); + public static final MenuType HOPPER = MenuType.register("hopper", HopperMenu::new); + public static final MenuType LECTERN = MenuType.register("lectern", (i, playerinventory) -> { +- return new LecternMenu(i); ++ return new LecternMenu(i, playerinventory); // CraftBukkit + }); + public static final MenuType LOOM = MenuType.register("loom", LoomMenu::new); + public static final MenuType MERCHANT = MenuType.register("merchant", MerchantMenu::new); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch new file mode 100644 index 0000000000..82bbe5e4c7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch @@ -0,0 +1,70 @@ +--- a/net/minecraft/world/inventory/MerchantContainer.java ++++ b/net/minecraft/world/inventory/MerchantContainer.java +@@ -5,11 +5,20 @@ + import net.minecraft.core.NonNullList; + import net.minecraft.world.Container; + import net.minecraft.world.ContainerHelper; ++import net.minecraft.world.entity.npc.AbstractVillager; ++import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.trading.Merchant; + import net.minecraft.world.item.trading.MerchantOffer; + import net.minecraft.world.item.trading.MerchantOffers; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.entity.CraftAbstractVillager; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class MerchantContainer implements Container { + +@@ -20,6 +29,46 @@ + public int selectionHint; + private int futureXp; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.itemStacks; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ this.merchant.setTradingPlayer((Player) null); // SPIGOT-4860 ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int i) { ++ this.maxStack = i; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return (this.merchant instanceof AbstractVillager) ? (CraftAbstractVillager) ((AbstractVillager) this.merchant).getBukkitEntity() : null; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations ++ } ++ // CraftBukkit end ++ + public MerchantContainer(Merchant merchant) { + this.itemStacks = NonNullList.withSize(3, ItemStack.EMPTY); + this.merchant = merchant; diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch new file mode 100644 index 0000000000..9fecebd95a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch @@ -0,0 +1,92 @@ +--- a/net/minecraft/world/inventory/MerchantMenu.java ++++ b/net/minecraft/world/inventory/MerchantMenu.java +@@ -12,6 +12,7 @@ + import net.minecraft.world.item.trading.Merchant; + import net.minecraft.world.item.trading.MerchantOffer; + import net.minecraft.world.item.trading.MerchantOffers; ++import org.bukkit.craftbukkit.inventory.view.CraftMerchantView; // CraftBukkit + + public class MerchantMenu extends AbstractContainerMenu { + +@@ -32,6 +33,19 @@ + private boolean showProgressBar; + private boolean canRestock; + ++ // CraftBukkit start ++ private CraftMerchantView bukkitEntity = null; ++ private Inventory player; ++ ++ @Override ++ public CraftMerchantView getBukkitView() { ++ if (this.bukkitEntity == null) { ++ this.bukkitEntity = new CraftMerchantView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(this.trader, this.tradeContainer), this, this.trader); ++ } ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + public MerchantMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new ClientSideMerchant(playerInventory.player)); + } +@@ -43,6 +57,7 @@ + this.addSlot(new Slot(this.tradeContainer, 0, 136, 37)); + this.addSlot(new Slot(this.tradeContainer, 1, 162, 37)); + this.addSlot(new MerchantResultSlot(playerInventory.player, merchant, this.tradeContainer, 2, 220, 37)); ++ this.player = playerInventory; // CraftBukkit - save player + this.addStandardInventorySlots(playerInventory, 108, 84); + } + +@@ -108,12 +123,12 @@ + + itemstack = itemstack1.copy(); + if (slot == 2) { +- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { ++ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + return ItemStack.EMPTY; + } + +- slot1.onQuickCraft(itemstack1, itemstack); +- this.playTradeSound(); ++ // slot1.onQuickCraft(itemstack1, itemstack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call ++ // this.playTradeSound(); + } else if (slot != 0 && slot != 1) { + if (slot >= 3 && slot < 30) { + if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { +@@ -126,6 +141,7 @@ + return ItemStack.EMPTY; + } + ++ if (slot != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2 + if (itemstack1.isEmpty()) { + slot1.setByPlayer(ItemStack.EMPTY); + } else { +@@ -137,13 +153,28 @@ + } + + slot1.onTake(player, itemstack1); ++ } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2 ++ if (slot == 2) { // is merchant result slot ++ slot1.onTake(player, itemstack1); ++ if (itemstack1.isEmpty()) { ++ slot1.set(ItemStack.EMPTY); ++ return ItemStack.EMPTY; ++ } ++ ++ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above ++ ++ slot1.onQuickCraft(itemstack1, itemstack); ++ this.playTradeSound(); ++ slot1.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + } + + return itemstack; + } + + private void playTradeSound() { +- if (!this.trader.isClientSide()) { ++ if (!this.trader.isClientSide() && this.trader instanceof Entity) { // CraftBukkit - SPIGOT-5035 + Entity entity = (Entity) this.trader; + + entity.level().playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch new file mode 100644 index 0000000000..f363fb2ef5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/inventory/MerchantResultSlot.java ++++ b/net/minecraft/world/inventory/MerchantResultSlot.java +@@ -47,13 +47,32 @@ + + @Override + public void onTake(Player player, ItemStack stack) { +- this.checkTakeAchievements(stack); ++ // this.checkTakeAchievements(stack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; move to after event is called and not cancelled + MerchantOffer merchantOffer = this.slots.getActiveOffer(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; ++ if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { ++ event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); ++ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { ++ event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true); ++ } ++ if (event != null) { ++ if (!event.callEvent()) { ++ stack.setCount(0); ++ event.getPlayer().updateInventory(); ++ return; ++ } ++ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ } ++ } ++ this.checkTakeAchievements(stack); ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (merchantOffer != null) { + ItemStack itemStack = this.slots.getItem(0); + ItemStack itemStack2 = this.slots.getItem(1); + if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { +- this.merchant.notifyTrade(merchantOffer); ++ this.merchant.processTrade(merchantOffer, event); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + player.awardStat(Stats.TRADED_WITH_VILLAGER); + this.slots.setItem(0, itemStack); + this.slots.setItem(1, itemStack2); diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch new file mode 100644 index 0000000000..e83e892d8e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java ++++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java +@@ -8,14 +8,32 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.entity.EnderChestBlockEntity; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end + + public class PlayerEnderChestContainer extends SimpleContainer { + + @Nullable + private EnderChestBlockEntity activeChest; ++ // CraftBukkit start ++ private final Player owner; + +- public PlayerEnderChestContainer() { ++ public InventoryHolder getBukkitOwner() { ++ return this.owner.getBukkitEntity(); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.activeChest != null ? CraftLocation.toBukkit(this.activeChest.getBlockPos(), this.activeChest.getLevel().getWorld()) : null; ++ } ++ ++ public PlayerEnderChestContainer(Player owner) { + super(27); ++ this.owner = owner; ++ // CraftBukkit end + } + + public void setActiveChest(EnderChestBlockEntity blockEntity) { diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch new file mode 100644 index 0000000000..a26bc470cf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch @@ -0,0 +1,67 @@ +--- a/net/minecraft/world/inventory/ResultContainer.java ++++ b/net/minecraft/world/inventory/ResultContainer.java +@@ -9,12 +9,64 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.RecipeHolder; + ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class ResultContainer implements Container, RecipeCraftingHolder { + + private final NonNullList itemStacks; + @Nullable + private RecipeHolder recipeUsed; + ++ // CraftBukkit start ++ private int maxStack = MAX_STACK; ++ ++ public java.util.List getContents() { ++ return this.itemStacks; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ // Paper start - Add missing InventoryHolders ++ if (this.holder == null && this.holderCreator != null) { ++ this.holder = this.holderCreator.get(); ++ } ++ return this.holder; // Result slots don't get an owner ++ // Paper end - Add missing InventoryHolders ++ } ++ ++ // Don't need a transaction; the InventoryCrafting keeps track of it for us ++ public void onOpen(CraftHumanEntity who) {} ++ public void onClose(CraftHumanEntity who) {} ++ public java.util.List getViewers() { ++ return new java.util.ArrayList(); ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ return null; ++ } ++ // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier holderCreator; ++ private @Nullable org.bukkit.inventory.InventoryHolder holder; ++ public ResultContainer(java.util.function.Supplier holderCreator) { ++ this(); ++ this.holderCreator = holderCreator; ++ } ++ // Paper end - Add missing InventoryHolders ++ + public ResultContainer() { + this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch new file mode 100644 index 0000000000..4efe4724bc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/inventory/ShulkerBoxMenu.java ++++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java +@@ -6,11 +6,30 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; ++// CraftBukkit end ++ + public class ShulkerBoxMenu extends AbstractContainerMenu { + + private static final int CONTAINER_SIZE = 27; + private final Container container; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity; ++ private Inventory player; + ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new CraftInventory(this.container), this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + public ShulkerBoxMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainer(27)); + } +@@ -19,6 +38,7 @@ + super(MenuType.SHULKER_BOX, syncId); + checkContainerSize(inventory, 27); + this.container = inventory; ++ this.player = playerInventory; // CraftBukkit - save player + inventory.startOpen(playerInventory.player); + boolean flag = true; + boolean flag1 = true; +@@ -34,6 +54,7 @@ + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return this.container.stillValid(player); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch new file mode 100644 index 0000000000..46bc8ef32c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch @@ -0,0 +1,66 @@ +--- a/net/minecraft/world/inventory/SmithingMenu.java ++++ b/net/minecraft/world/inventory/SmithingMenu.java +@@ -17,6 +17,7 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit + + public class SmithingMenu extends ItemCombinerMenu { + +@@ -34,6 +35,9 @@ + private final RecipePropertySet templateItemTest; + private final RecipePropertySet additionItemTest; + private final DataSlot hasRecipeError; ++ // CraftBukkit start ++ private CraftInventoryView bukkitEntity; ++ // CraftBukkit end + + public SmithingMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, ContainerLevelAccess.NULL); +@@ -111,13 +115,14 @@ + this.hasRecipeError.set(flag ? 1 : 0); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + @Override + public void createResult() { + SmithingRecipeInput smithingrecipeinput = this.createRecipeInput(); + Level world = this.level; +- Optional optional; ++ Optional> optional; // CraftBukkit - decompile error + + if (world instanceof ServerLevel worldserver) { + optional = worldserver.recipeAccess().getRecipeFor(RecipeType.SMITHING, smithingrecipeinput, worldserver); +@@ -129,7 +134,9 @@ + ItemStack itemstack = ((SmithingRecipe) recipeholder.value()).assemble(smithingrecipeinput, this.level.registryAccess()); + + this.resultSlots.setRecipeUsed(recipeholder); +- this.resultSlots.setItem(0, itemstack); ++ // CraftBukkit start ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(this.getBukkitView(), itemstack); ++ // CraftBukkit end + }, () -> { + this.resultSlots.setRecipeUsed((RecipeHolder) null); + this.resultSlots.setItem(0, ItemStack.EMPTY); +@@ -149,4 +156,18 @@ + public boolean hasRecipeError() { + return this.hasRecipeError.get() > 0; + } ++ ++ // CraftBukkit start ++ @Override ++ public CraftInventoryView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventorySmithing( ++ this.access.getLocation(), this.inputSlots, this.resultSlots); ++ this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch new file mode 100644 index 0000000000..95cd1f3220 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch @@ -0,0 +1,188 @@ +--- a/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/net/minecraft/world/inventory/StonecutterMenu.java +@@ -7,7 +7,6 @@ + import net.minecraft.world.Container; + import net.minecraft.world.SimpleContainer; + import net.minecraft.world.entity.player.Inventory; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.crafting.RecipeHolder; +@@ -17,6 +16,13 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Blocks; + ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter; ++import org.bukkit.craftbukkit.inventory.view.CraftStonecutterView; ++import org.bukkit.entity.Player; ++// CraftBukkit end ++ + public class StonecutterMenu extends AbstractContainerMenu { + + public static final int INPUT_SLOT = 0; +@@ -36,27 +42,49 @@ + Runnable slotUpdateListener; + public final Container container; + final ResultContainer resultContainer; ++ // CraftBukkit start ++ private CraftStonecutterView bukkitEntity = null; ++ private Player player; + ++ @Override ++ public CraftStonecutterView getBukkitView() { ++ if (this.bukkitEntity != null) { ++ return this.bukkitEntity; ++ } ++ ++ CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.container, this.resultContainer); ++ this.bukkitEntity = new CraftStonecutterView(this.player, inventory, this); ++ return this.bukkitEntity; ++ } ++ // CraftBukkit end ++ + public StonecutterMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, ContainerLevelAccess.NULL); + } + + public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.STONECUTTER, syncId); +- this.selectedRecipeIndex = DataSlot.standalone(); ++ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent + this.recipesForInput = SelectableRecipe.SingleInputSet.empty(); + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { + }; +- this.container = new SimpleContainer(1) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); + StonecutterMenu.this.slotsChanged(this); + StonecutterMenu.this.slotUpdateListener.run(); + } ++ ++ // CraftBukkit start ++ @Override ++ public Location getLocation() { ++ return context.getLocation(); ++ } ++ // CraftBukkit end + }; +- this.resultContainer = new ResultContainer(); ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders + this.access = context; + this.level = playerInventory.player.level(); + this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); +@@ -67,7 +95,7 @@ + } + + @Override +- public void onTake(Player player, ItemStack stack) { ++ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + stack.onCraftedBy(player.level(), player, stack.getCount()); + StonecutterMenu.this.resultContainer.awardUsedRecipes(player, this.getRelevantItems()); + ItemStack itemstack1 = StonecutterMenu.this.inputSlot.remove(1); +@@ -80,7 +108,7 @@ + long j = world.getGameTime(); + + if (StonecutterMenu.this.lastSoundTime != j) { +- world.playSound((Player) null, blockposition, SoundEvents.UI_STONECUTTER_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); ++ world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_STONECUTTER_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F); + StonecutterMenu.this.lastSoundTime = j; + } + +@@ -94,6 +122,7 @@ + }); + this.addStandardInventorySlots(playerInventory, 8, 84); + this.addDataSlot(this.selectedRecipeIndex); ++ this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit + } + + public int getSelectedRecipeIndex() { +@@ -113,18 +142,45 @@ + } + + @Override +- public boolean stillValid(Player player) { ++ public boolean stillValid(net.minecraft.world.entity.player.Player player) { ++ if (!this.checkReachable) return true; // CraftBukkit + return stillValid(this.access, player, Blocks.STONECUTTER); + } + + @Override +- public boolean clickMenuButton(Player player, int id) { ++ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (this.selectedRecipeIndex.get() == id) { + return false; + } else { + if (this.isValidRecipeIndex(id)) { +- this.selectedRecipeIndex.set(id); +- this.setupResultSlot(id); ++ // Paper start - Add PlayerStonecutterRecipeSelectEvent ++ int recipeIndex = id; ++ this.selectedRecipeIndex.set(recipeIndex); ++ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed ++ paperEventBlock: if (this.isValidRecipeIndex(id)) { ++ final Optional> recipe = this.recipesForInput.entries().get(id).recipe().recipe(); ++ if (recipe.isEmpty()) break paperEventBlock; // The recipe selected does not have an actual server recipe (presumably its the empty one). Cannot call the event, just break. ++ ++ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) recipe.get().toBukkitRecipe()); ++ if (!event.callEvent()) { ++ player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ ++ net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()); ++ if (!recipe.get().id().location().equals(key)) { // If the recipe did NOT stay the same ++ for (int newRecipeIndex = 0; newRecipeIndex < this.recipesForInput.entries().size(); newRecipeIndex++) { ++ if (this.recipesForInput.entries().get(newRecipeIndex).recipe().recipe().filter(r -> r.id().location().equals(key)).isPresent()) { ++ recipeIndex = newRecipeIndex; ++ break; ++ } ++ } ++ } ++ } ++ player.containerMenu.sendAllDataToRemote(); ++ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it ++ this.setupResultSlot(recipeIndex); ++ // Paper end - Add PlayerStonecutterRecipeSelectEvent + } + + return true; +@@ -144,6 +200,7 @@ + this.setupRecipeList(itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupRecipeList(ItemStack stack) { +@@ -158,7 +215,7 @@ + } + + void setupResultSlot(int selectedId) { +- Optional optional; ++ Optional> optional; // CraftBukkit - decompile error + + if (!this.recipesForInput.isEmpty() && this.isValidRecipeIndex(selectedId)) { + SelectableRecipe.SingleInputEntry selectablerecipe_a = (SelectableRecipe.SingleInputEntry) this.recipesForInput.entries().get(selectedId); +@@ -193,7 +250,7 @@ + } + + @Override +- public ItemStack quickMoveStack(Player player, int slot) { ++ public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) { + ItemStack itemstack = ItemStack.EMPTY; + Slot slot1 = (Slot) this.slots.get(slot); + +@@ -246,7 +303,7 @@ + } + + @Override +- public void removed(Player player) { ++ public void removed(net.minecraft.world.entity.player.Player player) { + super.removed(player); + this.resultContainer.removeItemNoUpdate(1); + this.access.execute((world, blockposition) -> { diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch new file mode 100644 index 0000000000..67ed6a0fb4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch @@ -0,0 +1,93 @@ +--- a/net/minecraft/world/inventory/TransientCraftingContainer.java ++++ b/net/minecraft/world/inventory/TransientCraftingContainer.java +@@ -3,11 +3,21 @@ + import java.util.Iterator; + import java.util.List; + import net.minecraft.core.NonNullList; ++import net.minecraft.world.Container; + import net.minecraft.world.ContainerHelper; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.player.StackedItemContents; + import net.minecraft.world.item.ItemStack; + ++// CraftBukkit start ++import java.util.List; ++import net.minecraft.world.item.crafting.RecipeHolder; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.inventory.InventoryType; ++// CraftBukkit end ++ + public class TransientCraftingContainer implements CraftingContainer { + + private final NonNullList items; +@@ -15,6 +25,68 @@ + private final int height; + private final AbstractContainerMenu menu; + ++ // CraftBukkit start - add fields ++ public List transaction = new java.util.ArrayList(); ++ private RecipeHolder currentRecipe; ++ public Container resultInventory; ++ private Player owner; ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public InventoryType getInvType() { ++ return this.items.size() == 4 ? InventoryType.CRAFTING : InventoryType.WORKBENCH; ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ public org.bukkit.inventory.InventoryHolder getOwner() { ++ return (this.owner == null) ? null : this.owner.getBukkitEntity(); ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ this.resultInventory.setMaxStackSize(size); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.menu instanceof CraftingMenu ? ((CraftingMenu) this.menu).access.getLocation() : this.owner.getBukkitEntity().getLocation(); ++ } ++ ++ @Override ++ public RecipeHolder getCurrentRecipe() { ++ return this.currentRecipe; ++ } ++ ++ @Override ++ public void setCurrentRecipe(RecipeHolder currentRecipe) { ++ this.currentRecipe = currentRecipe; ++ } ++ ++ public TransientCraftingContainer(AbstractContainerMenu container, int i, int j, Player player) { ++ this(container, i, j); ++ this.owner = player; ++ } ++ // CraftBukkit end ++ + public TransientCraftingContainer(AbstractContainerMenu handler, int width, int height) { + this(handler, width, height, NonNullList.withSize(width * height, ItemStack.EMPTY)); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch new file mode 100644 index 0000000000..5be546c2b6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/item/ArmorStandItem.java ++++ b/net/minecraft/world/item/ArmorStandItem.java +@@ -53,6 +53,12 @@ + float f = (float) Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F; + + entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityarmorstand).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end + worldserver.addFreshEntityWithPassengers(entityarmorstand); + world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); + entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer()); diff --git a/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch new file mode 100644 index 0000000000..858ea3302d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/item/AxeItem.java ++++ b/net/minecraft/world/item/AxeItem.java +@@ -67,6 +67,11 @@ + return InteractionResult.PASS; + } else { + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch new file mode 100644 index 0000000000..52e43aa9e4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch @@ -0,0 +1,109 @@ +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -10,9 +10,9 @@ + import net.minecraft.core.registries.Registries; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; +-import net.minecraft.sounds.SoundSource; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; +@@ -31,6 +31,10 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.shapes.CollisionContext; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.event.block.BlockCanBuildEvent; ++// CraftBukkit end + + public class BlockItem extends Item { + +@@ -62,6 +66,13 @@ + return InteractionResult.FAIL; + } else { + BlockState iblockdata = this.getPlacementState(blockactioncontext1); ++ // CraftBukkit start - special case for handling block placement with water lilies and snow buckets ++ org.bukkit.block.BlockState blockstate = null; ++ if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { ++ blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); ++ } ++ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - Reset placed block on exception ++ // CraftBukkit end + + if (iblockdata == null) { + return InteractionResult.FAIL; +@@ -76,9 +87,34 @@ + + if (iblockdata1.is(iblockdata.getBlock())) { + iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1); ++ // Paper start - Reset placed block on exception ++ try { + this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1); + BlockItem.updateBlockEntityComponents(world, blockposition, itemstack); ++ } catch (Exception e) { ++ oldBlockstate.update(true, false); ++ if (entityhuman instanceof ServerPlayer player) { ++ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e); ++ player.getBukkitEntity().kickPlayer("Packet processing error"); ++ return InteractionResult.FAIL; ++ } ++ throw e; // Rethrow exception if not placed by a player ++ } ++ // Paper end - Reset placed block on exception + iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack); ++ // CraftBukkit start ++ if (blockstate != null) { ++ org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent((ServerLevel) world, entityhuman, blockactioncontext1.getHand(), blockstate, blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { ++ blockstate.update(true, false); ++ ++ if (true) { // Paper - if the event is called here, the inventory should be updated ++ ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 ++ } ++ return InteractionResult.FAIL; ++ } ++ } ++ // CraftBukkit end + if (entityhuman instanceof ServerPlayer) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) entityhuman, blockposition, itemstack); + } +@@ -86,7 +122,7 @@ + + SoundType soundeffecttype = iblockdata1.getSoundType(); + +- world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); ++ if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1)); + itemstack.consume(1, entityhuman); + return InteractionResult.SUCCESS; +@@ -144,8 +180,16 @@ + protected boolean canPlace(BlockPlaceContext context, BlockState state) { + Player entityhuman = context.getPlayer(); + CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); ++ // CraftBukkit start - store default return ++ Level world = context.getLevel(); // Paper - Cancel hit for vanished players ++ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players ++ org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + +- return (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); ++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent ++ context.getLevel().getCraftServer().getPluginManager().callEvent(event); ++ ++ return event.isBuildable(); ++ // CraftBukkit end + } + + protected boolean mustSurvive() { +@@ -178,7 +222,7 @@ + return false; + } + +- if (tileentitytypes1.onlyOpCanSetNbt() && (player == null || !player.canUseGameMasterBlocks())) { ++ if (tileentitytypes1.onlyOpCanSetNbt() && (player == null || !(player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place"))))) { // Spigot - add permission + return false; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch new file mode 100644 index 0000000000..c8502fcea3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/item/BoatItem.java ++++ b/net/minecraft/world/item/BoatItem.java +@@ -58,6 +58,13 @@ + } + + if (movingobjectpositionblock.getType() == HitResult.Type.BLOCK) { ++ // CraftBukkit start - Boat placement ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(user, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, false, hand, movingobjectpositionblock.getLocation()); ++ ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + AbstractBoat abstractboat = this.getBoat(world, movingobjectpositionblock, itemstack, user); + + if (abstractboat == null) { +@@ -68,7 +75,15 @@ + return InteractionResult.FAIL; + } else { + if (!world.isClientSide) { +- world.addFreshEntity(abstractboat); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(world, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), user, abstractboat, hand).isCancelled()) { ++ return InteractionResult.FAIL; ++ } ++ ++ if (!world.addFreshEntity(abstractboat)) { ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + world.gameEvent((Entity) user, (Holder) GameEvent.ENTITY_PLACE, movingobjectpositionblock.getLocation()); + itemstack.consume(1, user); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch new file mode 100644 index 0000000000..4cf7f7eebf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/world/item/BoneMealItem.java ++++ b/net/minecraft/world/item/BoneMealItem.java +@@ -35,24 +35,30 @@ + + @Override + public InteractionResult useOn(UseOnContext context) { +- Level world = context.getLevel(); +- BlockPos blockposition = context.getClickedPos(); +- BlockPos blockposition1 = blockposition.relative(context.getClickedFace()); ++ // CraftBukkit start - extract bonemeal application logic to separate, static method ++ return BoneMealItem.applyBonemeal(context); ++ } + +- if (BoneMealItem.growCrop(context.getItemInHand(), world, blockposition)) { ++ public static InteractionResult applyBonemeal(UseOnContext itemactioncontext) { ++ // CraftBukkit end ++ Level world = itemactioncontext.getLevel(); ++ BlockPos blockposition = itemactioncontext.getClickedPos(); ++ BlockPos blockposition1 = blockposition.relative(itemactioncontext.getClickedFace()); ++ ++ if (BoneMealItem.growCrop(itemactioncontext.getItemInHand(), world, blockposition)) { + if (!world.isClientSide) { +- context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); ++ if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 + world.levelEvent(1505, blockposition, 15); + } + + return InteractionResult.SUCCESS; + } else { + BlockState iblockdata = world.getBlockState(blockposition); +- boolean flag = iblockdata.isFaceSturdy(world, blockposition, context.getClickedFace()); ++ boolean flag = iblockdata.isFaceSturdy(world, blockposition, itemactioncontext.getClickedFace()); + +- if (flag && BoneMealItem.growWaterPlant(context.getItemInHand(), world, blockposition1, context.getClickedFace())) { ++ if (flag && BoneMealItem.growWaterPlant(itemactioncontext.getItemInHand(), world, blockposition1, itemactioncontext.getClickedFace())) { + if (!world.isClientSide) { +- context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); ++ if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 + world.levelEvent(1505, blockposition1, 15); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch new file mode 100644 index 0000000000..6e51878d93 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch @@ -0,0 +1,168 @@ +--- a/net/minecraft/world/item/BucketItem.java ++++ b/net/minecraft/world/item/BucketItem.java +@@ -6,6 +6,8 @@ + import net.minecraft.core.Direction; + import net.minecraft.core.Holder; + import net.minecraft.core.particles.ParticleTypes; ++import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; +@@ -29,9 +31,17 @@ + import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.HitResult; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.DummyGeneratorAccess; ++import org.bukkit.event.player.PlayerBucketEmptyEvent; ++import org.bukkit.event.player.PlayerBucketFillEvent; ++// CraftBukkit end + + public class BucketItem extends Item implements DispensibleContainerItem { + ++ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack ++ + public final Fluid content; + + public BucketItem(Fluid fluid, Item.Properties settings) { +@@ -63,7 +73,18 @@ + + if (block instanceof BucketPickup) { + BucketPickup ifluidsource = (BucketPickup) block; ++ // CraftBukkit start ++ ItemStack dummyFluid = ifluidsource.pickupBlock(user, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); ++ if (dummyFluid.isEmpty()) return InteractionResult.FAIL; // Don't fire event if the bucket won't be filled. ++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand); + ++ if (event.isCancelled()) { ++ // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks ++ ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541 ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end ++ + itemstack1 = ifluidsource.pickupBlock(user, world, blockposition, iblockdata); + if (!itemstack1.isEmpty()) { + user.awardStat(Stats.ITEM_USED.get(this)); +@@ -71,7 +92,7 @@ + user.playSound(soundeffect, 1.0F, 1.0F); + }); + world.gameEvent((Entity) user, (Holder) GameEvent.FLUID_PICKUP, blockposition); +- ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, user, itemstack1); ++ ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, user, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit + + if (!world.isClientSide) { + CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer) user, itemstack1); +@@ -86,7 +107,7 @@ + iblockdata = world.getBlockState(blockposition); + BlockPos blockposition2 = iblockdata.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockposition : blockposition1; + +- if (this.emptyContents(user, world, blockposition2, movingobjectpositionblock)) { ++ if (this.emptyContents(user, world, blockposition2, movingobjectpositionblock, movingobjectpositionblock.getDirection(), blockposition, itemstack, hand)) { // CraftBukkit + this.checkExtraContent(user, world, itemstack, blockposition2); + if (user instanceof ServerPlayer) { + CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) user, blockposition2, itemstack); +@@ -106,6 +127,13 @@ + } + + public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) { ++ // Paper start - Fix PlayerBucketEmptyEvent result itemstack ++ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { ++ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; ++ itemLeftInHandAfterPlayerBucketEmptyEvent = null; ++ return itemInHand; ++ } ++ // Paper end - Fix PlayerBucketEmptyEvent result itemstack + return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack; + } + +@@ -114,6 +142,12 @@ + + @Override + public boolean emptyContents(@Nullable Player player, Level world, BlockPos pos, @Nullable BlockHitResult hitResult) { ++ // CraftBukkit start ++ return this.emptyContents(player, world, pos, hitResult, null, null, null, InteractionHand.MAIN_HAND); ++ } ++ ++ public boolean emptyContents(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack, InteractionHand enumhand) { ++ // CraftBukkit end + Fluid fluidtype = this.content; + + if (!(fluidtype instanceof FlowingFluid fluidtypeflowing)) { +@@ -126,7 +160,7 @@ + boolean flag1; + label70: + { +- iblockdata = world.getBlockState(pos); ++ iblockdata = world.getBlockState(blockposition); + block = iblockdata.getBlock(); + flag = iblockdata.canBeReplaced(this.content); + if (!iblockdata.isAir() && !flag) { +@@ -134,7 +168,7 @@ + { + if (block instanceof LiquidBlockContainer) { + ifluidcontainer = (LiquidBlockContainer) block; +- if (ifluidcontainer.canPlaceLiquid(player, world, pos, iblockdata, this.content)) { ++ if (ifluidcontainer.canPlaceLiquid(entityhuman, world, blockposition, iblockdata, this.content)) { + break label67; + } + } +@@ -149,14 +183,25 @@ + + boolean flag2 = flag1; + ++ // CraftBukkit start ++ if (flag2 && entityhuman != null) { ++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); ++ if (event.isCancelled()) { ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks ++ ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 ++ return false; ++ } ++ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack ++ } ++ // CraftBukkit end + if (!flag2) { +- return hitResult != null && this.emptyContents(player, world, hitResult.getBlockPos().relative(hitResult.getDirection()), (BlockHitResult) null); ++ return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit + } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { +- int i = pos.getX(); +- int j = pos.getY(); +- int k = pos.getZ(); ++ int i = blockposition.getX(); ++ int j = blockposition.getY(); ++ int k = blockposition.getZ(); + +- world.playSound(player, pos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); ++ world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); + + for (int l = 0; l < 8; ++l) { + world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); +@@ -167,20 +212,20 @@ + if (block instanceof LiquidBlockContainer) { + ifluidcontainer = (LiquidBlockContainer) block; + if (this.content == Fluids.WATER) { +- ifluidcontainer.placeLiquid(world, pos, iblockdata, fluidtypeflowing.getSource(false)); +- this.playEmptySound(player, world, pos); ++ ifluidcontainer.placeLiquid(world, blockposition, iblockdata, fluidtypeflowing.getSource(false)); ++ this.playEmptySound(entityhuman, world, blockposition); + return true; + } + } + + if (!world.isClientSide && flag && !iblockdata.liquid()) { +- world.destroyBlock(pos, true); ++ world.destroyBlock(blockposition, true); + } + +- if (!world.setBlock(pos, this.content.defaultFluidState().createLegacyBlock(), 11) && !iblockdata.getFluidState().isSource()) { ++ if (!world.setBlock(blockposition, this.content.defaultFluidState().createLegacyBlock(), 11) && !iblockdata.getFluidState().isSource()) { + return false; + } else { +- this.playEmptySound(player, world, pos); ++ this.playEmptySound(entityhuman, world, blockposition); + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch new file mode 100644 index 0000000000..d6f5a8f499 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch @@ -0,0 +1,48 @@ +--- a/net/minecraft/world/item/CrossbowItem.java ++++ b/net/minecraft/world/item/CrossbowItem.java +@@ -90,7 +90,14 @@ + public boolean releaseUsing(ItemStack stack, Level world, LivingEntity user, int remainingUseTicks) { + int i = this.getUseDuration(stack, user) - remainingUseTicks; + float f = getPowerForTime(i, stack, user); +- if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) { ++ // Paper start - Add EntityLoadCrossbowEvent ++ if (f >= 1.0F && !isCharged(stack)) { ++ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(user.getUsedItemHand())); ++ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) { ++ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ // Paper end - Add EntityLoadCrossbowEvent + CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack); + chargingSounds.end() + .ifPresent( +@@ -111,8 +118,14 @@ + } + } + +- private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { +- List list = draw(crossbow, shooter.getProjectile(crossbow), shooter); ++ @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { ++ // Paper start - Add EntityLoadCrossbowEvent ++ return CrossbowItem.tryLoadProjectiles(shooter, crossbow, true); ++ } ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow, boolean consume) { ++ List list = draw(crossbow, shooter.getProjectile(crossbow), shooter, consume); ++ // Paper end - Add EntityLoadCrossbowEvent + if (!list.isEmpty()) { + crossbow.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list)); + return true; +@@ -164,7 +177,11 @@ + @Override + protected Projectile createProjectile(Level world, LivingEntity shooter, ItemStack weaponStack, ItemStack projectileStack, boolean critical) { + if (projectileStack.is(Items.FIREWORK_ROCKET)) { +- return new FireworkRocketEntity(world, projectileStack, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true); ++ // Paper start ++ FireworkRocketEntity entity = new FireworkRocketEntity(world, projectileStack, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true); ++ entity.spawningEntity = shooter.getUUID(); // Paper ++ return entity; ++ // Paper end + } else { + Projectile projectile = super.createProjectile(world, shooter, weaponStack, projectileStack, critical); + if (projectile instanceof AbstractArrow abstractArrow) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch new file mode 100644 index 0000000000..b81a2063de --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/item/DebugStickItem.java ++++ b/net/minecraft/world/item/DebugStickItem.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.world.item; + + import java.util.Collection; +@@ -52,7 +53,7 @@ + } + + public boolean handleInteraction(Player player, BlockState state, LevelAccessor world, BlockPos pos, boolean update, ItemStack stack) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && !(player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.debugstick")) && !player.getBukkitEntity().hasPermission("minecraft.debugstick.always")) { // Spigot + return false; + } else { + Holder holder = state.getBlockHolder(); +@@ -92,7 +93,7 @@ + } + + private static > BlockState cycleState(BlockState state, Property property, boolean inverse) { +- return (BlockState) state.setValue(property, (Comparable) DebugStickItem.getRelative(property.getPossibleValues(), state.getValue(property), inverse)); ++ return (BlockState) state.setValue(property, DebugStickItem.getRelative(property.getPossibleValues(), state.getValue(property), inverse)); // CraftBukkit - decompile error + } + + private static T getRelative(Iterable elements, @Nullable T current, boolean inverse) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch new file mode 100644 index 0000000000..ca96f3388a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/item/DyeItem.java ++++ b/net/minecraft/world/item/DyeItem.java +@@ -12,6 +12,7 @@ + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.SignBlockEntity; ++import org.bukkit.event.entity.SheepDyeWoolEvent; // CraftBukkit + + public class DyeItem extends Item implements SignApplicator { + +@@ -30,7 +31,17 @@ + if (entitysheep.isAlive() && !entitysheep.isSheared() && entitysheep.getColor() != this.dyeColor) { + entitysheep.level().playSound(user, (Entity) entitysheep, SoundEvents.DYE_USE, SoundSource.PLAYERS, 1.0F, 1.0F); + if (!user.level().isClientSide) { +- entitysheep.setColor(this.dyeColor); ++ // CraftBukkit start ++ byte bColor = (byte) this.dyeColor.getId(); ++ SheepDyeWoolEvent event = new SheepDyeWoolEvent((org.bukkit.entity.Sheep) entitysheep.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData(bColor), (org.bukkit.entity.Player) user.getBukkitEntity()); ++ entitysheep.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ ++ entitysheep.setColor(DyeColor.byId((byte) event.getColor().getWoolData())); ++ // CraftBukkit end + stack.shrink(1); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch new file mode 100644 index 0000000000..6281e950e3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/world/item/EggItem.java ++++ b/net/minecraft/world/item/EggItem.java +@@ -25,13 +25,32 @@ + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemstack = user.getItemInHand(hand); + +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); // CraftBukkit - moved down + if (world instanceof ServerLevel worldserver) { +- Projectile.spawnProjectileFromRotation(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F); +- } ++ // CraftBukkit start ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownEgg.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemstack.consume(1, user); ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ // Paper end - PlayerLaunchProjectileEvent ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end ++ } ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch new file mode 100644 index 0000000000..89844fdef2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/item/EndCrystalItem.java ++++ b/net/minecraft/world/item/EndCrystalItem.java +@@ -30,7 +30,7 @@ + if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { + return InteractionResult.FAIL; + } else { +- BlockPos blockposition1 = blockposition.above(); ++ BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER + + if (!world.isEmptyBlock(blockposition1)) { + return InteractionResult.FAIL; +@@ -47,12 +47,18 @@ + EndCrystal entityendercrystal = new EndCrystal(world, d0 + 0.5D, d1, d2 + 0.5D); + + entityendercrystal.setShowBottom(false); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityendercrystal).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end + world.addFreshEntity(entityendercrystal); + world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.ENTITY_PLACE, blockposition1); + EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); + + if (enderdragonbattle != null) { +- enderdragonbattle.tryRespawn(); ++ enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch new file mode 100644 index 0000000000..c343069562 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch @@ -0,0 +1,56 @@ +--- a/net/minecraft/world/item/EnderEyeItem.java ++++ b/net/minecraft/world/item/EnderEyeItem.java +@@ -45,6 +45,11 @@ + return InteractionResult.SUCCESS; + } else { + BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true); ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition); + world.setBlock(blockposition, iblockdata1, 2); +@@ -62,7 +67,27 @@ + } + } + +- world.globalLevelEvent(1038, blockposition1.offset(1, 0, 1), 0); ++ // CraftBukkit start - Use relative location for far away sounds ++ // world.globalLevelEvent(1038, blockposition1.offset(1, 0, 1), 0); ++ int viewDistance = world.getCraftServer().getViewDistance() * 16; ++ BlockPos soundPos = blockposition1.offset(1, 0, 1); ++ final net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) world; // Paper - respect global sound events gamerule - ensured by isClientSide check above ++ for (ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ double deltaX = soundPos.getX() - player.getX(); ++ double deltaZ = soundPos.getZ() - player.getZ(); ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.endPortalSoundRadius); // Paper - respect global sound events gamerule ++ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance; ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, new BlockPos((int) relativeX, (int) soundPos.getY(), (int) relativeZ), 0, true)); ++ } else { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, soundPos, 0, true)); ++ } ++ } ++ // CraftBukkit end + } + + return InteractionResult.SUCCESS; +@@ -99,7 +124,11 @@ + entityendersignal.setItem(itemstack); + entityendersignal.signalTo(blockposition); + world.gameEvent((Holder) GameEvent.PROJECTILE_SHOOT, entityendersignal.position(), GameEvent.Context.of((Entity) user)); +- world.addFreshEntity(entityendersignal); ++ // CraftBukkit start ++ if (!world.addFreshEntity(entityendersignal)) { ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end + if (user instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) user; + diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch new file mode 100644 index 0000000000..e025880d4f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/item/EnderpearlItem.java ++++ b/net/minecraft/world/item/EnderpearlItem.java +@@ -23,13 +23,32 @@ + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemstack = user.getItemInHand(hand); + +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + if (world instanceof ServerLevel worldserver) { +- Projectile.spawnProjectileFromRotation(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ // CraftBukkit start ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ // Paper end - PlayerLaunchProjectileEvent ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } + } ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ // CraftBukkit end + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemstack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch new file mode 100644 index 0000000000..e16430df8d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch @@ -0,0 +1,54 @@ +--- a/net/minecraft/world/item/ExperienceBottleItem.java ++++ b/net/minecraft/world/item/ExperienceBottleItem.java +@@ -21,22 +21,38 @@ + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); +- world.playSound( +- null, +- user.getX(), +- user.getY(), +- user.getZ(), +- SoundEvents.EXPERIENCE_BOTTLE_THROW, +- SoundSource.NEUTRAL, +- 0.5F, +- 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) +- ); ++ // Paper - PlayerLaunchProjectileEvent - moved down + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownExperienceBottle = Projectile.spawnProjectileFromRotationDelayed(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownExperienceBottle.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownExperienceBottle.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemStack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound( ++ null, ++ user.getX(), ++ user.getY(), ++ user.getZ(), ++ SoundEvents.EXPERIENCE_BOTTLE_THROW, ++ SoundSource.NEUTRAL, ++ 0.5F, ++ 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) ++ ); ++ } else { ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch new file mode 100644 index 0000000000..ee72808622 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/item/FireChargeItem.java ++++ b/net/minecraft/world/item/FireChargeItem.java +@@ -40,12 +40,28 @@ + if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) { + blockposition = blockposition.relative(context.getClickedFace()); + if (BaseFireBlock.canBePlacedAt(world, blockposition, context.getHorizontalDirection())) { ++ // CraftBukkit start - fire BlockIgniteEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) { ++ if (!context.getPlayer().getAbilities().instabuild) { ++ context.getItemInHand().shrink(1); ++ } ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + this.playSound(world, blockposition); + world.setBlockAndUpdate(blockposition, BaseFireBlock.getState(world, blockposition)); + world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_PLACE, blockposition); + flag = true; + } + } else { ++ // CraftBukkit start - fire BlockIgniteEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) { ++ if (!context.getPlayer().getAbilities().instabuild) { ++ context.getItemInHand().shrink(1); ++ } ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + this.playSound(world, blockposition); + world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.setValue(BlockStateProperties.LIT, true)); + world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_CHANGE, blockposition); diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch new file mode 100644 index 0000000000..9d8f816a26 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/item/FireworkRocketItem.java ++++ b/net/minecraft/world/item/FireworkRocketItem.java +@@ -33,7 +33,7 @@ + ItemStack itemStack = context.getItemInHand(); + Vec3 vec3 = context.getClickLocation(); + Direction direction = context.getClickedFace(); +- Projectile.spawnProjectile( ++ final Projectile.Delayed fireworkRocketEntity = Projectile.spawnProjectileDelayed( // Paper - PlayerLaunchProjectileEvent + new FireworkRocketEntity( + level, + context.getPlayer(), +@@ -43,9 +43,14 @@ + itemStack + ), + serverLevel, +- itemStack ++ itemStack, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID() // Paper - firework api - assign spawning entity uuid + ); +- itemStack.shrink(1); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.projectile().getBukkitEntity()); ++ if (!event.callEvent() || !fireworkRocketEntity.attemptSpawn()) return InteractionResult.PASS; ++ if (event.shouldConsume() && !context.getPlayer().hasInfiniteMaterials()) itemStack.shrink(1); ++ else if (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory(); ++ // Paper end - PlayerLaunchProjectileEvent + } + + return InteractionResult.SUCCESS; +@@ -56,9 +61,19 @@ + if (user.isFallFlying()) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectile(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack); +- itemStack.consume(1, user); +- user.awardStat(Stats.ITEM_USED.get(this)); ++ // Paper start - PlayerElytraBoostEvent ++ final Projectile.Delayed delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack, f -> f.spawningEntity = user.getUUID()); // Paper - firework api - assign spawning entity uuid ++ com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); ++ if (event.callEvent() && delayed.attemptSpawn()) { ++ user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below ++ if (event.shouldConsume() && !user.hasInfiniteMaterials()) { ++ itemStack.shrink(1); // Moved up from below ++ } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } else { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ // Moved up consume/stat ++ // Paper end - PlayerElytraBoostEvent + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch new file mode 100644 index 0000000000..6c64321f95 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch @@ -0,0 +1,50 @@ +--- a/net/minecraft/world/item/FishingRodItem.java ++++ b/net/minecraft/world/item/FishingRodItem.java +@@ -14,6 +14,11 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start ++import org.bukkit.event.player.PlayerFishEvent; ++import org.bukkit.craftbukkit.CraftEquipmentSlot; ++// CraftBukkit end ++ + public class FishingRodItem extends Item { + + public FishingRodItem(Item.Properties settings) { +@@ -26,7 +31,7 @@ + + if (user.fishing != null) { + if (!world.isClientSide) { +- int i = user.fishing.retrieve(itemstack); ++ int i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent + + itemstack.hurtAndBreak(i, user, LivingEntity.getSlotForHand(hand)); + } +@@ -34,13 +39,24 @@ + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_RETRIEVE, SoundSource.NEUTRAL, 1.0F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + user.gameEvent(GameEvent.ITEM_INTERACT_FINISH); + } else { +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.FISHING_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + int j = (int) (EnchantmentHelper.getFishingTimeReduction(worldserver, itemstack, user) * 20.0F); + int k = EnchantmentHelper.getFishingLuckBonus(worldserver, itemstack, user); + +- Projectile.spawnProjectile(new FishingHook(user, world, k, j), worldserver, itemstack); ++ // CraftBukkit start ++ FishingHook entityfishinghook = new FishingHook(user, world, k, j); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((org.bukkit.entity.Player) user.getBukkitEntity(), null, (org.bukkit.entity.FishHook) entityfishinghook.getBukkitEntity(), CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.FISHING); ++ world.getCraftServer().getPluginManager().callEvent(playerFishEvent); ++ ++ if (playerFishEvent.isCancelled()) { ++ user.fishing = null; ++ return InteractionResult.PASS; ++ } ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ Projectile.spawnProjectile(entityfishinghook, worldserver, itemstack); ++ // CraftBukkit end + } + + user.awardStat(Stats.ITEM_USED.get(this)); diff --git a/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch new file mode 100644 index 0000000000..0d633ff507 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/world/item/FlintAndSteelItem.java ++++ b/net/minecraft/world/item/FlintAndSteelItem.java +@@ -37,6 +37,12 @@ + BlockPos blockposition1 = blockposition.relative(context.getClickedFace()); + + if (BaseFireBlock.canBePlacedAt(world, blockposition1, context.getHorizontalDirection())) { ++ // CraftBukkit start - Store the clicked block ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition1, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) { ++ context.getItemInHand().hurtAndBreak(1, entityhuman, LivingEntity.getSlotForHand(context.getHand())); ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + world.playSound(entityhuman, blockposition1, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F); + BlockState iblockdata1 = BaseFireBlock.getState(world, blockposition1); + +@@ -54,6 +60,12 @@ + return InteractionResult.FAIL; + } + } else { ++ // CraftBukkit start - Store the clicked block ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) { ++ context.getItemInHand().hurtAndBreak(1, entityhuman, LivingEntity.getSlotForHand(context.getHand())); ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + world.playSound(entityhuman, blockposition, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F); + world.setBlock(blockposition, (BlockState) iblockdata.setValue(BlockStateProperties.LIT, true), 11); + world.gameEvent((Entity) entityhuman, (Holder) GameEvent.BLOCK_CHANGE, blockposition); diff --git a/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch new file mode 100644 index 0000000000..79e498f9a5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch @@ -0,0 +1,67 @@ +--- a/net/minecraft/world/item/HangingEntityItem.java ++++ b/net/minecraft/world/item/HangingEntityItem.java +@@ -19,12 +19,16 @@ + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.decoration.PaintingVariant; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.component.CustomData; + import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start ++import org.bukkit.entity.Player; ++import org.bukkit.event.hanging.HangingPlaceEvent; ++// CraftBukkit end ++ + public class HangingEntityItem extends Item { + + private static final Component TOOLTIP_RANDOM_VARIANT = Component.translatable("painting.random").withStyle(ChatFormatting.GRAY); +@@ -40,7 +44,7 @@ + BlockPos blockposition = context.getClickedPos(); + Direction enumdirection = context.getClickedFace(); + BlockPos blockposition1 = blockposition.relative(enumdirection); +- Player entityhuman = context.getPlayer(); ++ net.minecraft.world.entity.player.Player entityhuman = context.getPlayer(); + ItemStack itemstack = context.getItemInHand(); + + if (entityhuman != null && !this.mayPlace(entityhuman, enumdirection, itemstack, blockposition1)) { +@@ -75,6 +79,19 @@ + + if (((HangingEntity) object).survives()) { + if (!world.isClientSide) { ++ // CraftBukkit start - fire HangingPlaceEvent ++ Player who = (context.getPlayer() == null) ? null : (Player) context.getPlayer().getBukkitEntity(); ++ org.bukkit.block.Block blockClicked = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection); ++ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand()); ++ ++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) ((HangingEntity) object).getBukkitEntity(), who, blockClicked, blockFace, hand, org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end + ((HangingEntity) object).playPlacementSound(); + world.gameEvent((Entity) entityhuman, (Holder) GameEvent.ENTITY_PLACE, ((HangingEntity) object).position()); + world.addFreshEntity((Entity) object); +@@ -88,7 +105,7 @@ + } + } + +- protected boolean mayPlace(Player player, Direction side, ItemStack stack, BlockPos pos) { ++ protected boolean mayPlace(net.minecraft.world.entity.player.Player player, Direction side, ItemStack stack, BlockPos pos) { + return !side.getAxis().isVertical() && player.mayUseItemAt(pos, side, stack); + } + +@@ -102,7 +119,7 @@ + + if (!customdata.isEmpty()) { + customdata.read(holderlookup_a.createSerializationContext(NbtOps.INSTANCE), Painting.VARIANT_MAP_CODEC).result().ifPresentOrElse((holder) -> { +- Optional optional = ((PaintingVariant) holder.value()).title(); ++ Optional optional = ((PaintingVariant) holder.value()).title(); // CraftBukkit - decompile error + + Objects.requireNonNull(tooltip); + optional.ifPresent(tooltip::add); diff --git a/paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch new file mode 100644 index 0000000000..02d0f745ad --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/item/HoneycombItem.java ++++ b/net/minecraft/world/item/HoneycombItem.java +@@ -74,6 +74,14 @@ + return getWaxed(blockState).map(state -> { + Player player = context.getPlayer(); + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { ++ if (!player.isCreative()) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer serverPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverPlayer, blockPos, itemStack); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch new file mode 100644 index 0000000000..f8d61e11c7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/item/ItemCooldowns.java ++++ b/net/minecraft/world/item/ItemCooldowns.java +@@ -56,6 +56,13 @@ + } + + public void addCooldown(ResourceLocation groupId, int duration) { ++ // Paper start - Item cooldown events ++ this.addCooldown(groupId, duration, true); ++ } ++ ++ public void addCooldown(ResourceLocation groupId, int duration, boolean callEvent) { ++ // Event called in server override ++ // Paper end - Item cooldown events + this.cooldowns.put(groupId, new ItemCooldowns.CooldownInstance(this.tickCount, this.tickCount + duration)); + this.onCooldownStarted(groupId, duration); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch new file mode 100644 index 0000000000..4409d94819 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch @@ -0,0 +1,630 @@ +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -23,6 +23,7 @@ + import net.minecraft.ChatFormatting; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; + import net.minecraft.core.Holder; + import net.minecraft.core.HolderLookup; + import net.minecraft.core.HolderSet; +@@ -46,10 +47,12 @@ + import net.minecraft.network.chat.MutableComponent; + import net.minecraft.network.codec.ByteBufCodecs; + import net.minecraft.network.codec.StreamCodec; ++import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; + import net.minecraft.resources.RegistryOps; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvent; ++import net.minecraft.sounds.SoundSource; + import net.minecraft.stats.Stats; + import net.minecraft.tags.TagKey; + import net.minecraft.util.ExtraCodecs; +@@ -70,7 +73,6 @@ + import net.minecraft.world.entity.ai.attributes.Attributes; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.inventory.ClickAction; + import net.minecraft.world.inventory.Slot; +@@ -88,26 +90,53 @@ + import net.minecraft.world.item.enchantment.EnchantmentHelper; + import net.minecraft.world.item.enchantment.ItemEnchantments; + import net.minecraft.world.item.enchantment.Repairable; +-import net.minecraft.world.level.ItemLike; +-import net.minecraft.world.level.Level; +-import net.minecraft.world.level.block.state.BlockState; +-import net.minecraft.world.level.block.state.pattern.BlockInWorld; + import net.minecraft.world.level.saveddata.maps.MapId; + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.Map; ++import java.util.Objects; ++import net.minecraft.world.level.ItemLike; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.BaseEntityBlock; ++import net.minecraft.world.level.block.BedBlock; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.SaplingBlock; ++import net.minecraft.world.level.block.SignBlock; ++import net.minecraft.world.level.block.SoundType; ++import net.minecraft.world.level.block.WitherSkullBlock; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.SignBlockEntity; ++import net.minecraft.world.level.block.entity.SkullBlockEntity; ++import net.minecraft.world.level.block.state.pattern.BlockInWorld; ++import net.minecraft.world.level.gameevent.GameEvent; ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.block.CapturedBlockState; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Player; ++import org.bukkit.event.block.BlockFertilizeEvent; ++import org.bukkit.event.player.PlayerItemDamageEvent; ++import org.bukkit.event.world.StructureGrowEvent; ++// CraftBukkit end ++ + public final class ItemStack implements DataComponentHolder { + + private static final List OP_NBT_WARNING = List.of(Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED), Component.translatable("item.op_warning.line3").withStyle(ChatFormatting.RED)); + public static final Codec CODEC = Codec.lazyInitialized(() -> { +- return RecordCodecBuilder.create((instance) -> { ++ return RecordCodecBuilder.create((instance) -> { // CraftBukkit - decompile error + return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), ExtraCodecs.intRange(1, 99).fieldOf("count").orElse(1).forGetter(ItemStack::getCount), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> { + return itemstack.components.asPatch(); + })).apply(instance, ItemStack::new); + }); + }); + public static final Codec SINGLE_ITEM_CODEC = Codec.lazyInitialized(() -> { +- return RecordCodecBuilder.create((instance) -> { ++ return RecordCodecBuilder.create((instance) -> { // CraftBukkit - decompile error + return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> { + return itemstack.components.asPatch(); + })).apply(instance, (holder, datacomponentpatch) -> { +@@ -132,20 +161,38 @@ + if (i <= 0) { + return ItemStack.EMPTY; + } else { +- Holder holder = (Holder) null.ITEM_STREAM_CODEC.decode(registryfriendlybytebuf); ++ Holder holder = (Holder) ITEM_STREAM_CODEC.decode(registryfriendlybytebuf); // CraftBukkit - decompile error + DataComponentPatch datacomponentpatch = (DataComponentPatch) DataComponentPatch.STREAM_CODEC.decode(registryfriendlybytebuf); + +- return new ItemStack(holder, i, datacomponentpatch); ++ // CraftBukkit start ++ ItemStack itemstack = new ItemStack(holder, i, datacomponentpatch); ++ if (false && !datacomponentpatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata ++ CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ } ++ return itemstack; ++ // CraftBukkit end + } + } + + public void encode(RegistryFriendlyByteBuf registryfriendlybytebuf, ItemStack itemstack) { +- if (itemstack.isEmpty()) { ++ if (itemstack.isEmpty() || itemstack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem() + registryfriendlybytebuf.writeVarInt(0); + } else { + registryfriendlybytebuf.writeVarInt(itemstack.getCount()); +- null.ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); ++ // Spigot start - filter ++ // itemstack = itemstack.copy(); ++ // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer with raw NBT being handled in metadata ++ // Spigot end ++ ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error ++ // Paper start - adventure; conditionally render translatable components ++ boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get(); ++ try { ++ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true); + DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.components.asPatch()); ++ } finally { ++ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev); ++ } ++ // Paper end - adventure; conditionally render translatable components + } + } + }; +@@ -187,7 +234,7 @@ + + return dataresult.isError() ? dataresult.map((unit) -> { + return stack; +- }) : (stack.getCount() > stack.getMaxStackSize() ? DataResult.error(() -> { ++ }) : (stack.getCount() > stack.getMaxStackSize() ? DataResult.error(() -> { // CraftBukkit - decompile error + int i = stack.getCount(); + + return "Item stack with stack size of " + i + " was larger than maximum: " + stack.getMaxStackSize(); +@@ -294,8 +341,9 @@ + j = itemstack.getMaxStackSize(); + } while (i <= j); + ++ int finalI = i, finalJ = j; // CraftBukkit - decompile error + return DataResult.error(() -> { +- return "Item stack with count of " + i + " was larger than maximum: " + j; ++ return "Item stack with count of " + finalI + " was larger than maximum: " + finalJ; // CraftBukkit - decompile error + }); + } + } +@@ -370,32 +418,200 @@ + } + + public InteractionResult useOn(UseOnContext context) { +- Player entityhuman = context.getPlayer(); ++ net.minecraft.world.entity.player.Player entityhuman = context.getPlayer(); + BlockPos blockposition = context.getClickedPos(); + + if (entityhuman != null && !entityhuman.getAbilities().mayBuild && !this.canPlaceOnBlockInAdventureMode(new BlockInWorld(context.getLevel(), blockposition, false))) { + return InteractionResult.PASS; + } else { + Item item = this.getItem(); +- InteractionResult enuminteractionresult = item.useOn(context); ++ // CraftBukkit start - handle all block place event logic here ++ DataComponentPatch oldData = this.components.asPatch(); ++ int oldCount = this.getCount(); ++ ServerLevel world = (ServerLevel) context.getLevel(); + ++ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement ++ world.captureBlockStates = true; ++ // special case bonemeal ++ if (item == Items.BONE_MEAL) { ++ world.captureTreeGeneration = true; ++ } ++ } ++ InteractionResult enuminteractionresult; ++ try { ++ enuminteractionresult = item.useOn(context); ++ } finally { ++ world.captureBlockStates = false; ++ } ++ DataComponentPatch newData = this.components.asPatch(); ++ int newCount = this.getCount(); ++ this.setCount(oldCount); ++ this.restorePatch(oldData); ++ if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) { ++ world.captureTreeGeneration = false; ++ Location location = CraftLocation.toBukkit(blockposition, world.getWorld()); ++ TreeType treeType = SaplingBlock.treeType; ++ SaplingBlock.treeType = null; ++ List blocks = new java.util.ArrayList<>(world.capturedBlockStates.values()); ++ world.capturedBlockStates.clear(); ++ StructureGrowEvent structureEvent = null; ++ if (treeType != null) { ++ boolean isBonemeal = this.getItem() == Items.BONE_MEAL; ++ structureEvent = new StructureGrowEvent(location, treeType, isBonemeal, (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List) blocks); ++ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); ++ } ++ ++ BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List) blocks); ++ fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); ++ org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); ++ ++ if (!fertilizeEvent.isCancelled()) { ++ // Change the stack to its new contents if it hasn't been tampered with. ++ if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) { ++ this.restorePatch(newData); ++ this.setCount(newCount); ++ } ++ for (CraftBlockState blockstate : blocks) { ++ // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest ++ CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed ++ } ++ entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat ++ } ++ ++ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return ++ return enuminteractionresult; ++ } ++ world.captureTreeGeneration = false; ++ + if (entityhuman != null && enuminteractionresult instanceof InteractionResult.Success) { + InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; + + if (enuminteractionresult_d.wasItemInteraction()) { +- entityhuman.awardStat(Stats.ITEM_USED.get(item)); ++ InteractionHand enumhand = context.getHand(); ++ org.bukkit.event.block.BlockPlaceEvent placeEvent = null; ++ List blocks = new java.util.ArrayList<>(world.capturedBlockStates.values()); ++ world.capturedBlockStates.clear(); ++ if (blocks.size() > 1) { ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ ++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { ++ enuminteractionresult = InteractionResult.FAIL; // cancel placement ++ // PAIL: Remove this when MC-99075 fixed ++ placeEvent.getPlayer().updateInventory(); ++ world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot ++ // revert back all captured blocks ++ world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 ++ world.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ for (BlockState blockstate : blocks) { ++ blockstate.update(true, false); ++ } ++ world.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ world.preventPoiUpdated = false; ++ ++ // Brute force all possible updates ++ // Paper start - Don't resync blocks ++ // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); ++ // for (Direction dir : Direction.values()) { ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); ++ // } ++ // Paper end - Don't resync blocks ++ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return ++ } else { ++ // Change the stack to its new contents if it hasn't been tampered with. ++ if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) { ++ this.restorePatch(newData); ++ this.setCount(newCount); ++ } ++ ++ for (Map.Entry e : world.capturedTileEntities.entrySet()) { ++ world.setBlockEntity(e.getValue()); ++ } ++ ++ for (BlockState blockstate : blocks) { ++ int updateFlag = ((CraftBlockState) blockstate).getFlag(); ++ net.minecraft.world.level.block.state.BlockState oldBlock = ((CraftBlockState) blockstate).getHandle(); ++ BlockPos newblockposition = ((CraftBlockState) blockstate).getPosition(); ++ net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition); ++ ++ if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically ++ block.onPlace(world, newblockposition, oldBlock, true, context); ++ } ++ ++ world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point ++ } ++ ++ if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled ++ BlockPos bp = blockposition; ++ if (!world.getBlockState(blockposition).canBeReplaced()) { ++ if (!world.getBlockState(blockposition).isSolid()) { ++ bp = null; ++ } else { ++ bp = bp.relative(context.getClickedFace()); ++ } ++ } ++ if (bp != null) { ++ BlockEntity te = world.getBlockEntity(bp); ++ if (te instanceof SkullBlockEntity) { ++ WitherSkullBlock.checkSpawn(world, bp, (SkullBlockEntity) te); ++ } ++ } ++ } ++ ++ // SPIGOT-4678 ++ if (this.item instanceof SignItem && SignItem.openSign != null) { ++ try { ++ if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) { ++ if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) { ++ blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Craftbukkit // Paper - Add PlayerOpenSignEvent ++ } ++ } ++ } finally { ++ SignItem.openSign = null; ++ } ++ } ++ ++ // SPIGOT-7315: Moved from BlockBed#setPlacedBy ++ if (placeEvent != null && this.item instanceof BedItem) { ++ BlockPos position = ((CraftBlock) placeEvent.getBlock()).getPosition(); ++ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); ++ ++ if (blockData.getBlock() instanceof BedBlock) { ++ world.blockUpdated(position, Blocks.AIR); ++ blockData.updateNeighbourShapes(world, position, 3); ++ } ++ } ++ ++ // SPIGOT-1288 - play sound stripped from ItemBlock ++ if (this.item instanceof BlockItem) { ++ // Paper start - Fix spigot sound playing for BlockItem ItemStacks ++ BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos(); ++ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); ++ SoundType soundeffecttype = blockData.getSoundType(); ++ // Paper end - Fix spigot sound playing for BlockItem ItemStacks ++ world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); ++ } ++ ++ entityhuman.awardStat(Stats.ITEM_USED.get(item)); ++ } + } + } ++ world.capturedTileEntities.clear(); ++ world.capturedBlockStates.clear(); ++ // CraftBukkit end + + return enuminteractionresult; + } + } + +- public float getDestroySpeed(BlockState state) { ++ public float getDestroySpeed(net.minecraft.world.level.block.state.BlockState state) { + return this.getItem().getDestroySpeed(this, state); + } + +- public InteractionResult use(Level world, Player user, InteractionHand hand) { ++ public InteractionResult use(Level world, net.minecraft.world.entity.player.Player user, InteractionHand hand) { + ItemStack itemstack = this.copy(); + boolean flag = this.getUseDuration(user) <= 0; + InteractionResult enuminteractionresult = this.getItem().use(world, user, hand); +@@ -490,27 +706,66 @@ + return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1; + } + +- public void hurtAndBreak(int amount, ServerLevel world, @Nullable ServerPlayer player, Consumer breakCallback) { +- int j = this.processDurabilityChange(amount, world, player); ++ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent ++ // Paper start - add force boolean overload ++ this.hurtAndBreak(amount, world, player, breakCallback, false); ++ } ++ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback, boolean force) { // Paper - Add EntityDamageItemEvent ++ // Paper end ++ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent ++ int j = this.processDurabilityChange(amount, world, player, force); // Paper ++ // CraftBukkit start ++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j, originalDamage); // Paper - Add EntityDamageItemEvent ++ event.getPlayer().getServer().getPluginManager().callEvent(event); + ++ if (j != event.getDamage() || event.isCancelled()) { ++ event.getPlayer().updateInventory(); ++ } ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ j = event.getDamage(); ++ // Paper start - Add EntityDamageItemEvent ++ } else if (player != null) { ++ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (!event.callEvent()) { ++ return; ++ } ++ j = event.getDamage(); ++ // Paper end - Add EntityDamageItemEvent ++ } ++ // CraftBukkit end ++ + if (j != 0) { + this.applyDamage(this.getDamageValue() + j, player, breakCallback); + } + + } + +- private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable ServerPlayer player) { +- return !this.isDamageableItem() ? 0 : (player != null && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); ++ private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent ++ // Paper start - itemstack damage api ++ return processDurabilityChange(baseDamage, world, player, false); + } ++ private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player, boolean force) { ++ return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); // Paper - Add EntityDamageItemEvent ++ // Paper end - itemstack damage api ++ } + +- private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer breakCallback) { +- if (player != null) { +- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage); ++ private void applyDamage(int damage, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent ++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent + } + + this.setDamageValue(damage); + if (this.isBroken()) { + Item item = this.getItem(); ++ // CraftBukkit start - Check for item breaking ++ if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent ++ } ++ // CraftBukkit end + + this.shrink(1); + breakCallback.accept(item); +@@ -518,7 +773,7 @@ + + } + +- public void hurtWithoutBreaking(int amount, Player player) { ++ public void hurtWithoutBreaking(int amount, net.minecraft.world.entity.player.Player player) { + if (player instanceof ServerPlayer entityplayer) { + int j = this.processDurabilityChange(amount, entityplayer.serverLevel(), entityplayer); + +@@ -535,6 +790,11 @@ + } + + public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) { ++ // Paper start - add param to skip infinite mats check ++ this.hurtAndBreak(amount, entity, slot, false); ++ } ++ public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) { ++ // Paper end - add param to skip infinite mats check + Level world = entity.level(); + + if (world instanceof ServerLevel worldserver) { +@@ -546,9 +806,9 @@ + entityplayer = null; + } + +- this.hurtAndBreak(amount, worldserver, entityplayer, (item) -> { +- entity.onEquippedItemBroken(item, slot); +- }); ++ this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent ++ if (slot != null) entity.onEquippedItemBroken(item, slot); // Paper - itemstack damage API - do not process entity related callbacks when damaging from API ++ }, force); // Paper - itemstack damage API + } + + } +@@ -580,11 +840,11 @@ + return this.getItem().getBarColor(this); + } + +- public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, Player player) { ++ public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player) { + return this.getItem().overrideStackedOnOther(this, slot, clickType, player); + } + +- public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, Player player, SlotAccess cursorStackReference) { ++ public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player, SlotAccess cursorStackReference) { + return this.getItem().overrideOtherStackedOnMe(this, stack, slot, clickType, player, cursorStackReference); + } + +@@ -592,8 +852,8 @@ + Item item = this.getItem(); + + if (item.hurtEnemy(this, target, user)) { +- if (user instanceof Player) { +- Player entityhuman = (Player) user; ++ if (user instanceof net.minecraft.world.entity.player.Player) { ++ net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) user; + + entityhuman.awardStat(Stats.ITEM_USED.get(item)); + } +@@ -608,7 +868,7 @@ + this.getItem().postHurtEnemy(this, target, user); + } + +- public void mineBlock(Level world, BlockState state, BlockPos pos, Player miner) { ++ public void mineBlock(Level world, net.minecraft.world.level.block.state.BlockState state, BlockPos pos, net.minecraft.world.entity.player.Player miner) { + Item item = this.getItem(); + + if (item.mineBlock(this, world, state, pos, miner)) { +@@ -617,11 +877,11 @@ + + } + +- public boolean isCorrectToolForDrops(BlockState state) { ++ public boolean isCorrectToolForDrops(net.minecraft.world.level.block.state.BlockState state) { + return this.getItem().isCorrectToolForDrops(this, state); + } + +- public InteractionResult interactLivingEntity(Player user, LivingEntity entity, InteractionHand hand) { ++ public InteractionResult interactLivingEntity(net.minecraft.world.entity.player.Player user, LivingEntity entity, InteractionHand hand) { + return this.getItem().interactLivingEntity(this, user, entity, hand); + } + +@@ -736,7 +996,7 @@ + + } + +- public void onCraftedBy(Level world, Player player, int amount) { ++ public void onCraftedBy(Level world, net.minecraft.world.entity.player.Player player, int amount) { + player.awardStat(Stats.ITEM_CRAFTED.get(this.getItem()), amount); + this.getItem().onCraftedBy(this, world, player); + } +@@ -768,7 +1028,13 @@ + + public boolean useOnRelease() { + return this.getItem().useOnRelease(this); ++ } ++ ++ // CraftBukkit start ++ public void restorePatch(DataComponentPatch datacomponentpatch) { ++ this.components.restorePatch(datacomponentpatch); + } ++ // CraftBukkit end + + @Nullable + public T set(DataComponentType type, @Nullable T value) { +@@ -805,6 +1071,25 @@ + this.getItem().verifyComponentsAfterLoad(this); + } + } ++ ++ // Paper start - (this is just a good no conflict location) ++ public org.bukkit.inventory.ItemStack asBukkitMirror() { ++ return CraftItemStack.asCraftMirror(this); ++ } ++ public org.bukkit.inventory.ItemStack asBukkitCopy() { ++ return CraftItemStack.asCraftMirror(this.copy()); ++ } ++ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) { ++ return CraftItemStack.asNMSCopy(itemstack); ++ } ++ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack; ++ public org.bukkit.inventory.ItemStack getBukkitStack() { ++ if (bukkitStack == null || bukkitStack.handle != this) { ++ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); ++ } ++ return bukkitStack; ++ } ++ // Paper end + + public void applyComponents(DataComponentPatch changes) { + this.components.applyPatch(changes); +@@ -858,7 +1143,7 @@ + } + + private void addToTooltip(DataComponentType componentType, Item.TooltipContext context, Consumer textConsumer, TooltipFlag type) { +- T t0 = (TooltipProvider) this.get(componentType); ++ T t0 = (T) this.get(componentType); // CraftBukkit - decompile error + + if (t0 != null) { + t0.addToTooltip(context, textConsumer, type); +@@ -866,7 +1151,7 @@ + + } + +- public List getTooltipLines(Item.TooltipContext context, @Nullable Player player, TooltipFlag type) { ++ public List getTooltipLines(Item.TooltipContext context, @Nullable net.minecraft.world.entity.player.Player player, TooltipFlag type) { + boolean flag = this.getItem().shouldPrintOpWarning(this, player); + + if (!type.isCreative() && this.has(DataComponents.HIDE_TOOLTIP)) { +@@ -941,7 +1226,7 @@ + } + } + +- private void addAttributeTooltips(Consumer textConsumer, @Nullable Player player) { ++ private void addAttributeTooltips(Consumer textConsumer, @Nullable net.minecraft.world.entity.player.Player player) { + ItemAttributeModifiers itemattributemodifiers = (ItemAttributeModifiers) this.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY); + + if (itemattributemodifiers.showInTooltip()) { +@@ -966,7 +1251,7 @@ + } + } + +- private void addModifierTooltip(Consumer textConsumer, @Nullable Player player, Holder attribute, AttributeModifier modifier) { ++ private void addModifierTooltip(Consumer textConsumer, @Nullable net.minecraft.world.entity.player.Player player, Holder attribute, AttributeModifier modifier) { + double d0 = modifier.amount(); + boolean flag = false; + +@@ -1091,6 +1376,19 @@ + EnchantmentHelper.forEachModifier(this, slot, attributeModifierConsumer); + } + ++ // CraftBukkit start ++ @Deprecated ++ public void setItem(Item item) { ++ this.bukkitStack = null; // Paper ++ this.item = item; ++ // Paper start - change base component prototype ++ final DataComponentPatch patch = this.getComponentsPatch(); ++ this.components = new PatchedDataComponentMap(this.item.components()); ++ this.applyComponents(patch); ++ // Paper end - change base component prototype ++ } ++ // CraftBukkit end ++ + public Component getDisplayName() { + MutableComponent ichatmutablecomponent = Component.empty().append(this.getHoverName()); + +@@ -1153,7 +1451,7 @@ + } + + public void consume(int amount, @Nullable LivingEntity entity) { +- if (entity == null || !entity.hasInfiniteMaterials()) { ++ if ((entity == null || !entity.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit + this.shrink(amount); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch new file mode 100644 index 0000000000..163c53a0d4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/item/ItemUtils.java ++++ b/net/minecraft/world/item/ItemUtils.java +@@ -41,7 +41,15 @@ + public static void onContainerDestroyed(ItemEntity itemEntity, Iterable contents) { + Level level = itemEntity.level(); + if (!level.isClientSide) { +- contents.forEach(stack -> level.addFreshEntity(new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack))); ++ // Paper start - call EntityDropItemEvent ++ contents.forEach(stack -> { ++ ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack); ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity()); ++ if (event.callEvent()) { ++ level.addFreshEntity(droppedItem); ++ } ++ }); ++ // Paper end - call EntityDropItemEvent + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch new file mode 100644 index 0000000000..e45ad43774 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch @@ -0,0 +1,92 @@ +--- a/net/minecraft/world/item/LeadItem.java ++++ b/net/minecraft/world/item/LeadItem.java +@@ -18,6 +18,11 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.AABB; ++// CraftBukkit start ++import org.bukkit.craftbukkit.CraftEquipmentSlot; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.hanging.HangingPlaceEvent; ++// CraftBukkit end + + public class LeadItem extends Item { + +@@ -35,37 +40,70 @@ + Player entityhuman = context.getPlayer(); + + if (!world.isClientSide && entityhuman != null) { +- return LeadItem.bindPlayerMobs(entityhuman, world, blockposition); ++ return LeadItem.bindPlayerMobs(entityhuman, world, blockposition, context.getHand()); // CraftBukkit - Pass hand + } + } + + return InteractionResult.PASS; + } + +- public static InteractionResult bindPlayerMobs(Player player, Level world, BlockPos pos) { ++ public static InteractionResult bindPlayerMobs(Player entityhuman, Level world, BlockPos blockposition, net.minecraft.world.InteractionHand enumhand) { // CraftBukkit - Add EnumHand + LeashFenceKnotEntity entityleash = null; +- List list = LeadItem.leashableInArea(world, pos, (leashable) -> { +- return leashable.getLeashHolder() == player; ++ List list = LeadItem.leashableInArea(world, blockposition, (leashable) -> { ++ return leashable.getLeashHolder() == entityhuman; + }); + + Leashable leashable; + +- for (Iterator iterator = list.iterator(); iterator.hasNext(); leashable.setLeashedTo(entityleash, true)) { ++ for (Iterator iterator = list.iterator(); iterator.hasNext();) { // CraftBukkit - handle setLeashedTo at end of loop + leashable = (Leashable) iterator.next(); + if (entityleash == null) { +- entityleash = LeashFenceKnotEntity.getOrCreateKnot(world, pos); ++ entityleash = LeashFenceKnotEntity.getOrCreateKnot(world, blockposition); ++ ++ // CraftBukkit start - fire HangingPlaceEvent ++ org.bukkit.inventory.EquipmentSlot hand = CraftEquipmentSlot.getHand(enumhand); ++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityleash.getBukkitEntity(), entityhuman != null ? (org.bukkit.entity.Player) entityhuman.getBukkitEntity() : null, CraftBlock.at(world, blockposition), org.bukkit.block.BlockFace.SELF, hand); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ entityleash.discard(null); // CraftBukkit - add Bukkit remove cause ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + entityleash.playPlacementSound(); + } ++ ++ // CraftBukkit start ++ if (leashable instanceof Entity leashed) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(leashed, entityleash, entityhuman, enumhand).isCancelled()) { ++ iterator.remove(); ++ continue; ++ } ++ } ++ ++ leashable.setLeashedTo(entityleash, true); ++ // CraftBukkit end + } + + if (!list.isEmpty()) { +- world.gameEvent((Holder) GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of((Entity) player)); ++ world.gameEvent((Holder) GameEvent.BLOCK_ATTACH, blockposition, GameEvent.Context.of((Entity) entityhuman)); + return InteractionResult.SUCCESS_SERVER; + } else { ++ // CraftBukkit start- remove leash if we do not leash any entity because of the cancelled event ++ if (entityleash != null) { ++ entityleash.discard(null); ++ } ++ // CraftBukkit end + return InteractionResult.PASS; + } + } + ++ // CraftBukkit start ++ public static InteractionResult bindPlayerMobs(Player player, Level world, BlockPos pos) { ++ return LeadItem.bindPlayerMobs(player, world, pos, net.minecraft.world.InteractionHand.MAIN_HAND); ++ } ++ // CraftBukkit end ++ + public static List leashableInArea(Level world, BlockPos pos, Predicate predicate) { + double d0 = 7.0D; + int i = pos.getX(); diff --git a/paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch new file mode 100644 index 0000000000..04fd231da8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/item/LingeringPotionItem.java ++++ b/net/minecraft/world/item/LingeringPotionItem.java +@@ -24,6 +24,10 @@ + + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final InteractionResult wrapper = super.use(world, user, hand); ++ if (wrapper instanceof InteractionResult.Fail) return wrapper; ++ // Paper end - PlayerLaunchProjectileEvent + world.playSound( + null, + user.getX(), +@@ -34,6 +38,6 @@ + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- return super.use(world, user, hand); ++ return wrapper; // Paper - PlayerLaunchProjectileEvent + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch new file mode 100644 index 0000000000..5374da2aa0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/item/MapItem.java ++++ b/net/minecraft/world/item/MapItem.java +@@ -97,8 +97,8 @@ + int r = (j / i + o - 64) * i; + int s = (k / i + p - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- LevelChunk levelChunk = world.getChunk(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); +- if (!levelChunk.isEmpty()) { ++ LevelChunk levelChunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); // Paper - Maps shouldn't load chunks ++ if (levelChunk != null && !levelChunk.isEmpty()) { // Paper - Maps shouldn't load chunks + int t = 0; + double e = 0.0; + if (world.dimensionType().hasCeiling()) { +@@ -205,7 +205,7 @@ + + for (int n = 0; n < 128; n++) { + for (int o = 0; o < 128; o++) { +- Holder holder = world.getBiome(mutableBlockPos.set((l + o) * i, 0, (m + n) * i)); ++ Holder holder = world.getUncachedNoiseBiome((l + o) * i, 0, (m + n) * i); // Paper - Perf: Use seed based lookup for treasure maps + bls[n * 128 + o] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch new file mode 100644 index 0000000000..f97004f5f9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/item/MinecartItem.java ++++ b/net/minecraft/world/item/MinecartItem.java +@@ -67,7 +67,13 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- worldserver.addFreshEntity(entityminecartabstract); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityminecartabstract).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.FAIL; ++ } ++ // CraftBukkit end ++ if (!worldserver.addFreshEntity(entityminecartabstract)) return InteractionResult.PASS; // CraftBukkit + worldserver.gameEvent((Holder) GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of(context.getPlayer(), worldserver.getBlockState(blockposition.below()))); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch new file mode 100644 index 0000000000..735436752e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/item/NameTagItem.java ++++ b/net/minecraft/world/item/NameTagItem.java +@@ -18,8 +18,13 @@ + Component component = stack.get(DataComponents.CUSTOM_NAME); + if (component != null && entity.getType().canSerialize() && entity.canBeNameTagged()) { + if (!user.level().isClientSide && entity.isAlive()) { +- entity.setCustomName(component); +- if (entity instanceof Mob mob) { ++ // Paper start - Add PlayerNameEntityEvent ++ io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true); ++ if (!event.callEvent()) return InteractionResult.PASS; ++ LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); ++ newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); ++ if (event.isPersistent() && newEntity instanceof Mob mob) { ++ // Paper end - Add PlayerNameEntityEvent + mob.setPersistenceRequired(); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch new file mode 100644 index 0000000000..185bcf8992 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/item/PotionItem.java ++++ b/net/minecraft/world/item/PotionItem.java +@@ -42,6 +42,12 @@ + PotionContents potionContents = itemStack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); + BlockState blockState = level.getBlockState(blockPos); + if (context.getClickedFace() != Direction.DOWN && blockState.is(BlockTags.CONVERTABLE_TO_MUD) && potionContents.is(Potions.WATER)) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, Blocks.MUD.defaultBlockState())) { ++ player.containerMenu.sendAllDataToRemote(); ++ return InteractionResult.PASS; ++ } ++ // Paper end + level.playSound(null, blockPos, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F); + player.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemStack, player, new ItemStack(Items.GLASS_BOTTLE))); + player.awardStat(Stats.ITEM_USED.get(itemStack.getItem())); diff --git a/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch new file mode 100644 index 0000000000..dfdb8d27c6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/item/ProjectileWeaponItem.java ++++ b/net/minecraft/world/item/ProjectileWeaponItem.java +@@ -54,9 +54,25 @@ + float f6 = f4 + f5 * (float) ((i + 1) / 2) * f3; + + f5 = -f5; +- Projectile.spawnProjectile(this.createProjectile(world, shooter, stack, itemstack1, critical), world, itemstack1, (iprojectile) -> { +- this.shootProjectile(shooter, iprojectile, i, speed, divergence, f6, target); +- }); ++ // CraftBukkit start ++ Projectile iprojectile = this.createProjectile(world, shooter, stack, itemstack1, critical); ++ this.shootProjectile(shooter, iprojectile, i, speed, divergence, f6, target); ++ ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, stack, itemstack1, iprojectile, hand, speed, true); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == iprojectile.getBukkitEntity()) { ++ if (Projectile.spawnProjectile(iprojectile, world, itemstack1).isRemoved()) { ++ if (shooter instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) shooter).getBukkitEntity().updateInventory(); ++ } ++ return; ++ } ++ } ++ // CraftBukkit end + stack.hurtAndBreak(this.getDurabilityUse(itemstack1), shooter, LivingEntity.getSlotForHand(hand)); + if (stack.isEmpty()) { + break; +@@ -93,6 +109,11 @@ + } + + protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter) { ++ // Paper start ++ return draw(stack, projectileStack, shooter, true); ++ } ++ protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter, boolean consume) { ++ // Paper end + if (projectileStack.isEmpty()) { + return List.of(); + } else { +@@ -112,7 +133,7 @@ + ItemStack itemstack2 = projectileStack.copy(); + + for (int k = 0; k < j; ++k) { +- ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0); ++ ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0 || !consume); // Paper + + if (!itemstack3.isEmpty()) { + list.add(itemstack3); diff --git a/paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch new file mode 100644 index 0000000000..b9e17d595b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch @@ -0,0 +1,43 @@ +--- a/net/minecraft/world/item/ServerItemCooldowns.java ++++ b/net/minecraft/world/item/ServerItemCooldowns.java +@@ -11,7 +11,40 @@ + this.player = player; + } + ++ // Paper start - Add PlayerItemCooldownEvent + @Override ++ public void addCooldown(ItemStack item, int duration) { ++ final ResourceLocation cooldownGroup = this.getCooldownGroup(item); ++ final io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent( ++ this.player.getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(item.getItem()), ++ org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cooldownGroup), ++ duration ++ ); ++ if (event.callEvent()) { ++ super.addCooldown(cooldownGroup, event.getCooldown(), false); ++ } ++ } ++ ++ @Override ++ public void addCooldown(ResourceLocation groupId, int duration, boolean callEvent) { ++ if (callEvent) { ++ final io.papermc.paper.event.player.PlayerItemGroupCooldownEvent event = new io.papermc.paper.event.player.PlayerItemGroupCooldownEvent( ++ this.player.getBukkitEntity(), ++ org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(groupId), ++ duration ++ ); ++ if (!event.callEvent()) { ++ return; ++ } ++ ++ duration = event.getCooldown(); ++ } ++ super.addCooldown(groupId, duration, false); ++ } ++ // Paper end - Add PlayerItemCooldownEvent ++ ++ @Override + protected void onCooldownStarted(ResourceLocation groupId, int duration) { + super.onCooldownStarted(groupId, duration); + this.player.connection.send(new ClientboundCooldownPacket(groupId, duration)); diff --git a/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch new file mode 100644 index 0000000000..0840475938 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/item/ShovelItem.java ++++ b/net/minecraft/world/item/ShovelItem.java +@@ -46,20 +46,29 @@ + Player player = context.getPlayer(); + BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); + BlockState blockState3 = null; ++ Runnable afterAction = null; // Paper + if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { +- level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); ++ afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper + blockState3 = blockState2; + } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { ++ afterAction = () -> { // Paper + if (!level.isClientSide()) { + level.levelEvent(null, 1009, blockPos, 0); + } + + CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState); ++ }; // Paper + blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)); + } + + if (blockState3 != null) { + if (!level.isClientSide) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) { ++ return InteractionResult.PASS; ++ } ++ afterAction.run(); ++ // Paper end + level.setBlock(blockPos, blockState3, 11); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3)); + if (player != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch new file mode 100644 index 0000000000..50c0cdf7d1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/item/SignItem.java ++++ b/net/minecraft/world/item/SignItem.java +@@ -13,6 +13,8 @@ + + public class SignItem extends StandingAndWallBlockItem { + ++ public static BlockPos openSign; // CraftBukkit ++ + public SignItem(Block standingBlock, Block wallBlock, Item.Properties settings) { + super(standingBlock, wallBlock, Direction.DOWN, settings); + } +@@ -35,7 +37,10 @@ + if (block instanceof SignBlock) { + SignBlock blocksign = (SignBlock) block; + +- blocksign.openTextEdit(player, tileentitysign, true); ++ // CraftBukkit start - SPIGOT-4678 ++ // blocksign.openTextEdit(entityhuman, tileentitysign, true); ++ SignItem.openSign = pos; ++ // CraftBukkit end + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch new file mode 100644 index 0000000000..bb5a1788d6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/item/SnowballItem.java ++++ b/net/minecraft/world/item/SnowballItem.java +@@ -25,13 +25,30 @@ + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemstack = user.getItemInHand(hand); + +- world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ // CraftBukkit start - moved down ++ // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + if (world instanceof ServerLevel worldserver) { +- Projectile.spawnProjectileFromRotation(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity()); ++ if (event.callEvent() && snowball.attemptSpawn()) { ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ // Paper end - PlayerLaunchProjectileEvent ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ } else { if (user instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent - return fail ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } return InteractionResult.FAIL; } // Paper - PlayerLaunchProjectileEvent - return fail ++ // CraftBukkit end + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemstack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up ++ // itemstack.consume(1, entityhuman); // CraftBukkit - moved up + return InteractionResult.SUCCESS; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch new file mode 100644 index 0000000000..a33a635c7a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/item/SpawnEggItem.java ++++ b/net/minecraft/world/item/SpawnEggItem.java +@@ -63,6 +63,8 @@ + EntityType entitytypes; + + if (tileentity instanceof Spawner) { ++ if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation ++ + Spawner spawner = (Spawner) tileentity; + + entitytypes = this.getType(world.registryAccess(), itemstack); +@@ -176,10 +178,10 @@ + return Optional.empty(); + } else { + ((Mob) object).moveTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F); +- world.addFreshEntityWithPassengers((Entity) object); ++ world.addFreshEntityWithPassengers((Entity) object, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit + ((Mob) object).setCustomName((Component) stack.get(DataComponents.CUSTOM_NAME)); + stack.consume(1, user); +- return Optional.of(object); ++ return Optional.of((Mob) object); // CraftBukkit - decompile error + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch new file mode 100644 index 0000000000..bdf8aca4d5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/item/SplashPotionItem.java ++++ b/net/minecraft/world/item/SplashPotionItem.java +@@ -14,6 +14,10 @@ + + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final InteractionResult wrapper = super.use(world, user, hand); ++ if (wrapper instanceof InteractionResult.Fail) return wrapper; ++ // Paper end - PlayerLaunchProjectileEvent + world.playSound( + null, + user.getX(), +@@ -24,6 +28,6 @@ + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- return super.use(world, user, hand); ++ return wrapper; // Paper - PlayerLaunchProjectileEvent + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch new file mode 100644 index 0000000000..9e1a576a36 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/world/item/StandingAndWallBlockItem.java ++++ b/net/minecraft/world/item/StandingAndWallBlockItem.java +@@ -4,12 +4,17 @@ + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.item.context.BlockPlaceContext; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.shapes.CollisionContext; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.event.block.BlockCanBuildEvent; ++// CraftBukkit end + + public class StandingAndWallBlockItem extends BlockItem { + +@@ -49,7 +54,19 @@ + } + } + +- return iblockdata1 != null && world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty()) ? iblockdata1 : null; ++ // CraftBukkit start ++ if (iblockdata1 != null) { ++ boolean defaultReturn = world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty()); ++ org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; ++ ++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent ++ context.getLevel().getCraftServer().getPluginManager().callEvent(event); ++ ++ return (event.isBuildable()) ? iblockdata1 : null; ++ } else { ++ return null; ++ } ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch new file mode 100644 index 0000000000..13c480eabd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/item/ThrowablePotionItem.java ++++ b/net/minecraft/world/item/ThrowablePotionItem.java +@@ -22,11 +22,28 @@ + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownPotion.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemStack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - move up + return InteractionResult.SUCCESS; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch new file mode 100644 index 0000000000..a23995cfe3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/item/TridentItem.java ++++ b/net/minecraft/world/item/TridentItem.java +@@ -86,18 +86,37 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- stack.hurtWithoutBreaking(1, entityhuman); ++ // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down + if (f == 0.0F) { +- ThrownTrident entitythrowntrident = (ThrownTrident) Projectile.spawnProjectileFromRotation(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity()); ++ if (!event.callEvent() || !tridentDelayed.attemptSpawn()) { ++ // CraftBukkit start ++ // Paper end - PlayerLaunchProjectileEvent ++ if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); ++ } ++ return false; ++ } ++ ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent ++ if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent ++ entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved ++ // CraftBukkit end + + if (entityhuman.hasInfiniteMaterials()) { + entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; +- } else { ++ } else if (event.shouldConsume()) { // Paper - PlayerLaunchProjectileEvent + entityhuman.getInventory().removeItem(stack); + } + + world.playSound((Player) null, (Entity) entitythrowntrident, (SoundEvent) holder.value(), SoundSource.PLAYERS, 1.0F, 1.0F); + return true; ++ // CraftBukkit start - SPIGOT-5458 also need in this branch :( ++ } else { ++ stack.hurtWithoutBreaking(1, entityhuman); ++ // CraftBukkkit end + } + } + +@@ -112,6 +131,7 @@ + f3 *= f / f6; + f4 *= f / f6; + f5 *= f / f6; ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(entityhuman, stack, f3, f4, f5); // CraftBukkit + entityhuman.push((double) f3, (double) f4, (double) f5); + entityhuman.startAutoSpinAttack(20, 8.0F, stack); + if (entityhuman.onGround()) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch new file mode 100644 index 0000000000..8fd7be1352 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/item/WindChargeItem.java ++++ b/net/minecraft/world/item/WindChargeItem.java +@@ -27,7 +27,7 @@ + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation( ++ final Projectile.Delayed windCharge = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent + (world2, shooter, stack) -> new WindCharge(user, world, user.position().x(), user.getEyePosition().y(), user.position().z()), + serverLevel, + itemStack, +@@ -36,6 +36,21 @@ + PROJECTILE_SHOOT_POWER, + 1.0F + ); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) windCharge.projectile().getBukkitEntity()); ++ if (!event.callEvent() || !windCharge.attemptSpawn()) { ++ user.containerMenu.sendAllDataToRemote(); ++ if (user instanceof net.minecraft.server.level.ServerPlayer player) { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundCooldownPacket(user.getCooldowns().getCooldownGroup(itemStack), 0)); // prevent visual desync of cooldown on the slot ++ } ++ return InteractionResult.FAIL; ++ } ++ ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ if (event.shouldConsume()) itemStack.consume(1, user); ++ else if (!user.hasInfiniteMaterials()) { ++ user.containerMenu.sendAllDataToRemote(); ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + + world.playSound( +@@ -48,8 +63,6 @@ + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); + return InteractionResult.SUCCESS; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch new file mode 100644 index 0000000000..f29d9ca860 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/WrittenBookItem.java ++++ b/net/minecraft/world/item/WrittenBookItem.java +@@ -41,7 +41,7 @@ + + public static boolean resolveBookComponents(ItemStack book, CommandSourceStack commandSource, @Nullable Player player) { + WrittenBookContent writtenBookContent = book.get(DataComponents.WRITTEN_BOOK_CONTENT); +- if (writtenBookContent != null && !writtenBookContent.resolved()) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && writtenBookContent != null && !writtenBookContent.resolved()) { // Paper - Disable component selector resolving in books by default + WrittenBookContent writtenBookContent2 = writtenBookContent.resolve(commandSource, player); + if (writtenBookContent2 != null) { + book.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent2); diff --git a/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch new file mode 100644 index 0000000000..c9c0b22f57 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/world/item/alchemy/PotionBrewing.java ++++ b/net/minecraft/world/item/alchemy/PotionBrewing.java +@@ -19,6 +19,7 @@ + private final List containers; + private final List> potionMixes; + private final List> containerMixes; ++ private final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap customMixes = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes + + PotionBrewing(List potionTypes, List> potionRecipes, List> itemRecipes) { + this.containers = potionTypes; +@@ -27,7 +28,7 @@ + } + + public boolean isIngredient(ItemStack stack) { +- return this.isContainerIngredient(stack) || this.isPotionIngredient(stack); ++ return this.isContainerIngredient(stack) || this.isPotionIngredient(stack) || this.isCustomIngredient(stack); // Paper - Custom Potion Mixes + } + + private boolean isContainer(ItemStack stack) { +@@ -71,6 +72,11 @@ + } + + public boolean hasMix(ItemStack input, ItemStack ingredient) { ++ // Paper start - Custom Potion Mixes ++ if (this.hasCustomMix(input, ingredient)) { ++ return true; ++ } ++ // Paper end - Custom Potion Mixes + return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient)); + } + +@@ -103,6 +109,13 @@ + if (input.isEmpty()) { + return input; + } else { ++ // Paper start - Custom Potion Mixes ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return mix.result().copy(); ++ } ++ } ++ // Paper end - Custom Potion Mixes + Optional> optional = input.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion(); + if (optional.isEmpty()) { + return input; +@@ -190,6 +203,50 @@ + builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING); + } + ++ // Paper start - Custom Potion Mixes ++ public boolean isCustomIngredient(ItemStack stack) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.ingredient().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public boolean isCustomInput(ItemStack stack) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private boolean hasCustomMix(ItemStack input, ItemStack ingredient) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public void addPotionMix(io.papermc.paper.potion.PotionMix mix) { ++ if (this.customMixes.containsKey(mix.getKey())) { ++ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); ++ } ++ this.customMixes.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); ++ } ++ ++ public boolean removePotionMix(org.bukkit.NamespacedKey key) { ++ return this.customMixes.remove(key) != null; ++ } ++ ++ public PotionBrewing reload(FeatureFlagSet flags) { ++ return bootstrap(flags); ++ } ++ // Paper end - Custom Potion Mixes ++ + public static class Builder { + private final List containers = new ArrayList<>(); + private final List> potionMixes = new ArrayList<>(); diff --git a/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch new file mode 100644 index 0000000000..163fbb5056 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/item/alchemy/PotionContents.java ++++ b/net/minecraft/world/item/alchemy/PotionContents.java +@@ -93,7 +93,7 @@ + } + + public PotionContents withEffectAdded(MobEffectInstance customEffect) { +- return new PotionContents(this.potion, this.customColor, Util.copyAndAdd(this.customEffects, (Object) customEffect), this.customName); ++ return new PotionContents(this.potion, this.customColor, Util.copyAndAdd(this.customEffects, customEffect), this.customName); // CraftBukkit - decompile error + } + + public int getColor() { +@@ -172,7 +172,7 @@ + if (((MobEffect) mobeffect.getEffect().value()).isInstantenous()) { + ((MobEffect) mobeffect.getEffect().value()).applyInstantenousEffect(worldserver, entityhuman2, entityhuman2, user, mobeffect.getAmplifier(), 1.0D); + } else { +- user.addEffect(mobeffect); ++ user.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit + } + + }); diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch new file mode 100644 index 0000000000..a163ec68c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/item/component/Consumable.java ++++ b/net/minecraft/world/item/component/Consumable.java +@@ -29,6 +29,11 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start ++import net.minecraft.world.item.Items; ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end ++ + public record Consumable(float consumeSeconds, ItemUseAnimation animation, Holder sound, boolean hasConsumeParticles, List onConsumeEffects) { + + public static final float DEFAULT_CONSUME_SECONDS = 1.6F; +@@ -69,8 +74,19 @@ + consumablelistener.onConsume(world, user, stack, this); + }); + if (!world.isClientSide) { ++ // CraftBukkit start ++ EntityPotionEffectEvent.Cause cause; ++ if (stack.is(Items.MILK_BUCKET)) { ++ cause = EntityPotionEffectEvent.Cause.MILK; ++ } else if (stack.is(Items.POTION)) { ++ cause = EntityPotionEffectEvent.Cause.POTION_DRINK; ++ } else { ++ cause = EntityPotionEffectEvent.Cause.FOOD; ++ } ++ + this.onConsumeEffects.forEach((consumeeffect) -> { +- consumeeffect.apply(world, stack, user); ++ consumeeffect.apply(world, stack, user, cause); ++ // CraftBukkit end + }); + } + +@@ -79,6 +95,17 @@ + return stack; + } + ++ // CraftBukkit start ++ public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack) { ++ final java.util.List> packets = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Paper - properly resend entities - collect packets for bundle ++ itemstack.getAllOfType(ConsumableListener.class).forEach((consumablelistener) -> { ++ consumablelistener.cancelUsingItem(entityplayer, itemstack, packets); // Paper - properly resend entities - collect packets for bundle ++ }); ++ entityplayer.server.getPlayerList().sendActiveEffects(entityplayer, packets::add); // Paper - properly resend entities - collect packets for bundle ++ entityplayer.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(packets)); ++ } ++ // CraftBukkit end ++ + public boolean canConsume(LivingEntity user, ItemStack stack) { + FoodProperties foodinfo = (FoodProperties) stack.get(DataComponents.FOOD); + diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch new file mode 100644 index 0000000000..2e31354cba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch @@ -0,0 +1,9 @@ +--- a/net/minecraft/world/item/component/ConsumableListener.java ++++ b/net/minecraft/world/item/component/ConsumableListener.java +@@ -7,4 +7,6 @@ + public interface ConsumableListener { + + void onConsume(Level world, LivingEntity user, ItemStack stack, Consumable consumable); ++ ++ default void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List> collectedPackets) {} // CraftBukkit // Paper - properly resend entities - collect packets for bundle + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch new file mode 100644 index 0000000000..73089c0170 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/item/component/CustomData.java ++++ b/net/minecraft/world/item/component/CustomData.java +@@ -34,7 +34,17 @@ + private static final Logger LOGGER = LogUtils.getLogger(); + public static final CustomData EMPTY = new CustomData(new CompoundTag()); + private static final String TYPE_TAG = "id"; +- public static final Codec CODEC = Codec.withAlternative(CompoundTag.CODEC, TagParser.AS_CODEC) ++ // Paper start - Item serialization as json ++ public static ThreadLocal SERIALIZE_CUSTOM_AS_SNBT = ThreadLocal.withInitial(() -> false); ++ public static final Codec CODEC = Codec.either(CompoundTag.CODEC, TagParser.AS_CODEC) ++ .xmap(com.mojang.datafixers.util.Either::unwrap, data -> { // Both will be used for deserialization, but we decide which one to use for serialization ++ if (!SERIALIZE_CUSTOM_AS_SNBT.get()) { ++ return com.mojang.datafixers.util.Either.left(data); // First codec ++ } else { ++ return com.mojang.datafixers.util.Either.right(data); // Second codec ++ } ++ }) ++ // Paper end - Item serialization as json + .xmap(CustomData::new, component -> component.tag); + public static final Codec CODEC_WITH_ID = CODEC.validate( + component -> component.getUnsafe().contains("id", 8) ? DataResult.success(component) : DataResult.error(() -> "Missing id for entity in: " + component) diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch new file mode 100644 index 0000000000..8516839b42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/item/component/DeathProtection.java ++++ b/net/minecraft/world/item/component/DeathProtection.java +@@ -15,6 +15,10 @@ + import net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect; + import net.minecraft.world.item.consume_effects.ConsumeEffect; + ++// CraftBukkit start ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end ++ + public record DeathProtection(List deathEffects) { + + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { +@@ -29,7 +33,7 @@ + while (iterator.hasNext()) { + ConsumeEffect consumeeffect = (ConsumeEffect) iterator.next(); + +- consumeeffect.apply(entity.level(), stack, entity); ++ consumeeffect.apply(entity.level(), stack, entity, EntityPotionEffectEvent.Cause.TOTEM); // CraftBukkit + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch new file mode 100644 index 0000000000..79d33fc6bf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/component/LodestoneTracker.java ++++ b/net/minecraft/world/item/component/LodestoneTracker.java +@@ -29,7 +29,7 @@ + return this; + } else { + BlockPos blockPos = this.target.get().pos(); +- return world.isInWorldBounds(blockPos) && world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos) ++ return world.isInWorldBounds(blockPos) && (!world.hasChunkAt(blockPos) || world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks + ? this + : new LodestoneTracker(Optional.empty(), true); + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch new file mode 100644 index 0000000000..57338edcbf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/item/component/OminousBottleAmplifier.java ++++ b/net/minecraft/world/item/component/OminousBottleAmplifier.java +@@ -28,8 +28,14 @@ + + @Override + public void onConsume(Level world, LivingEntity user, ItemStack stack, Consumable consumable) { +- user.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); ++ user.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below + } ++ // Paper start - properly resend entities - collect packets for bundle ++ @Override ++ public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List> collectedPackets) { ++ collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(entityplayer.getId(), MobEffects.BAD_OMEN)); ++ } ++ // Paper end - properly resend entities - collect packets for bundle + + @Override + public void addToTooltip(Item.TooltipContext context, Consumer tooltip, TooltipFlag type) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch new file mode 100644 index 0000000000..c0d2bc1d79 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/item/component/ResolvableProfile.java ++++ b/net/minecraft/world/item/component/ResolvableProfile.java +@@ -20,9 +20,10 @@ + instance -> instance.group( + ExtraCodecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ResolvableProfile::name), + UUIDUtil.CODEC.optionalFieldOf("id").forGetter(ResolvableProfile::id), ++ UUIDUtil.STRING_CODEC.lenientOptionalFieldOf("Id").forGetter($ -> Optional.empty()), // Paper + ExtraCodecs.PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ResolvableProfile::properties) + ) +- .apply(instance, ResolvableProfile::new) ++ .apply(instance, (s, uuid, uuid2, propertyMap) -> new ResolvableProfile(s, uuid2.or(() -> uuid), propertyMap)) // Paper + ); + public static final Codec CODEC = Codec.withAlternative( + FULL_CODEC, ExtraCodecs.PLAYER_NAME, name -> new ResolvableProfile(Optional.of(name), Optional.empty(), new PropertyMap()) +@@ -49,7 +50,7 @@ + if (this.isResolved()) { + return CompletableFuture.completedFuture(this); + } else { +- return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get()).thenApply(optional -> { ++ return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).thenApply(optional -> { // Paper - player profile events + GameProfile gameProfile = optional.orElseGet(() -> new GameProfile(this.id.get(), this.name.orElse(""))); + return new ResolvableProfile(gameProfile); + }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(profile -> { diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch new file mode 100644 index 0000000000..2836856f9c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/world/item/component/SuspiciousStewEffects.java ++++ b/net/minecraft/world/item/component/SuspiciousStewEffects.java +@@ -29,7 +29,7 @@ + public static final StreamCodec STREAM_CODEC = SuspiciousStewEffects.Entry.STREAM_CODEC.apply(ByteBufCodecs.list()).map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects); + + public SuspiciousStewEffects withEffectAdded(SuspiciousStewEffects.Entry stewEffect) { +- return new SuspiciousStewEffects(Util.copyAndAdd(this.effects, (Object) stewEffect)); ++ return new SuspiciousStewEffects(Util.copyAndAdd(this.effects, stewEffect)); // CraftBukkit - decompile error + } + + @Override +@@ -44,7 +44,16 @@ + + } + ++ // CraftBukkit start + @Override ++ public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List> collectedPackets) { // Paper - properly resend entities - collect packets for bundle ++ for (SuspiciousStewEffects.Entry suspicioussteweffects_a : this.effects) { ++ collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(entityplayer.getId(), suspicioussteweffects_a.effect())); // Paper - bundlize packets ++ } ++ } ++ // CraftBukkit end ++ ++ @Override + public void addToTooltip(Item.TooltipContext context, Consumer tooltip, TooltipFlag type) { + if (type.isCreative()) { + List list = new ArrayList(); diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch new file mode 100644 index 0000000000..2551be5478 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java +@@ -12,6 +12,9 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end + + public record ApplyStatusEffectsConsumeEffect(List effects, float probability) implements ConsumeEffect { + +@@ -38,8 +41,8 @@ + } + + @Override +- public boolean apply(Level world, ItemStack stack, LivingEntity user) { +- if (user.getRandom().nextFloat() >= this.probability) { ++ public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { // CraftBukkit ++ if (entityliving.getRandom().nextFloat() >= this.probability) { + return false; + } else { + boolean flag = false; +@@ -48,7 +51,7 @@ + while (iterator.hasNext()) { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + +- if (user.addEffect(new MobEffectInstance(mobeffect))) { ++ if (entityliving.addEffect(new MobEffectInstance(mobeffect), cause)) { // CraftBukkit + flag = true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch new file mode 100644 index 0000000000..35168c5339 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java +@@ -6,6 +6,9 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end + + public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect { + +@@ -19,7 +22,9 @@ + } + + @Override +- public boolean apply(Level world, ItemStack stack, LivingEntity user) { +- return user.removeAllEffects(); ++ // CraftBukkit start ++ public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { ++ return entityliving.removeAllEffects(cause); ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch new file mode 100644 index 0000000000..c5d56842c7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/item/consume_effects/ConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/ConsumeEffect.java +@@ -11,6 +11,9 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end + + public interface ConsumeEffect { + +@@ -19,8 +22,16 @@ + + ConsumeEffect.Type getType(); + +- boolean apply(Level world, ItemStack stack, LivingEntity user); ++ // CraftBukkit start ++ default boolean apply(Level world, ItemStack stack, LivingEntity user) { ++ return this.apply(world, stack, user, EntityPotionEffectEvent.Cause.UNKNOWN); ++ } + ++ default boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { ++ return this.apply(world, itemstack, entityliving); ++ } ++ // CraftBukkit end ++ + public static record Type(MapCodec codec, StreamCodec streamCodec) { + + public static final ConsumeEffect.Type APPLY_EFFECTS = register("apply_effects", ApplyStatusEffectsConsumeEffect.CODEC, ApplyStatusEffectsConsumeEffect.STREAM_CODEC); diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch new file mode 100644 index 0000000000..c5eacaef69 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java +@@ -14,6 +14,9 @@ + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityPotionEffectEvent; ++// CraftBukkit end + + public record RemoveStatusEffectsConsumeEffect(HolderSet effects) implements ConsumeEffect { + +@@ -32,14 +35,14 @@ + } + + @Override +- public boolean apply(Level world, ItemStack stack, LivingEntity user) { ++ public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { // CraftBukkit + boolean flag = false; + Iterator iterator = this.effects.iterator(); + + while (iterator.hasNext()) { + Holder holder = (Holder) iterator.next(); + +- if (user.removeEffect(holder)) { ++ if (entityliving.removeEffect(holder, cause)) { // CraftBukkit + flag = true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch new file mode 100644 index 0000000000..b419363bd1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java +@@ -53,7 +53,16 @@ + + Vec3 vec3d = user.position(); + +- if (user.randomTeleport(d0, d1, d2, true)) { ++ // CraftBukkit start - handle canceled status of teleport event ++ java.util.Optional status = user.randomTeleport(d0, d1, d2, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT); ++ ++ if (!status.isPresent()) { ++ // teleport event was canceled, no more tries ++ break; ++ } ++ ++ if (status.get()) { ++ // CraftBukkit end + world.gameEvent((Holder) GameEvent.TELEPORT, vec3d, GameEvent.Context.of((Entity) user)); + SoundEvent soundeffect; + SoundSource soundcategory; diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch new file mode 100644 index 0000000000..26a37bbc25 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/item/crafting/BlastingRecipe.java ++++ b/net/minecraft/world/item/crafting/BlastingRecipe.java +@@ -4,6 +4,14 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftBlastingRecipe; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class BlastingRecipe extends AbstractCookingRecipe { + + public BlastingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) { +@@ -43,4 +51,17 @@ + + return recipebookcategory; + } ++ ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result()); ++ ++ CraftBlastingRecipe recipe = new CraftBlastingRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime()); ++ recipe.setGroup(this.group()); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ return recipe; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch new file mode 100644 index 0000000000..039e214990 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/item/crafting/CampfireCookingRecipe.java ++++ b/net/minecraft/world/item/crafting/CampfireCookingRecipe.java +@@ -4,6 +4,14 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftCampfireRecipe; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class CampfireCookingRecipe extends AbstractCookingRecipe { + + public CampfireCookingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) { +@@ -29,4 +37,17 @@ + public RecipeBookCategory recipeBookCategory() { + return RecipeBookCategories.CAMPFIRE; + } ++ ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result()); ++ ++ CraftCampfireRecipe recipe = new CraftCampfireRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime()); ++ recipe.setGroup(this.group()); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ return recipe; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch new file mode 100644 index 0000000000..773c84fb8b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch @@ -0,0 +1,54 @@ +--- a/net/minecraft/world/item/crafting/CustomRecipe.java ++++ b/net/minecraft/world/item/crafting/CustomRecipe.java +@@ -8,6 +8,15 @@ + import net.minecraft.network.RegistryFriendlyByteBuf; + import net.minecraft.network.codec.StreamCodec; + ++// CraftBukkit start ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftComplexRecipe; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public abstract class CustomRecipe implements CraftingRecipe { + + private final CraftingBookCategory category; +@@ -34,6 +43,19 @@ + @Override + public abstract RecipeSerializer getSerializer(); + ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(ItemStack.EMPTY); ++ ++ CraftComplexRecipe recipe = new CraftComplexRecipe(id, result, this); ++ recipe.setGroup(this.group()); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ return recipe; ++ } ++ // CraftBukkit end ++ + public static class Serializer implements RecipeSerializer { + + private final MapCodec codec; +@@ -41,13 +63,13 @@ + + public Serializer(CustomRecipe.Serializer.Factory factory) { + this.codec = RecordCodecBuilder.mapCodec((instance) -> { +- P1 p1 = instance.group(CraftingBookCategory.CODEC.fieldOf("category").orElse(CraftingBookCategory.MISC).forGetter(CraftingRecipe::category)); ++ P1, CraftingBookCategory> p1 = instance.group(CraftingBookCategory.CODEC.fieldOf("category").orElse(CraftingBookCategory.MISC).forGetter(CraftingRecipe::category)); // CraftBukkit - decompile error + + Objects.requireNonNull(factory); + return p1.apply(instance, factory::create); + }); + StreamCodec streamcodec = CraftingBookCategory.STREAM_CODEC; +- Function function = CraftingRecipe::category; ++ Function function = CraftingRecipe::category; // CraftBukkit - decompile error + + Objects.requireNonNull(factory); + this.streamCodec = StreamCodec.composite(streamcodec, function, factory::create); diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch new file mode 100644 index 0000000000..1d00386837 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch @@ -0,0 +1,66 @@ +--- a/net/minecraft/world/item/crafting/Ingredient.java ++++ b/net/minecraft/world/item/crafting/Ingredient.java +@@ -20,6 +20,10 @@ + import net.minecraft.world.item.Items; + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.level.ItemLike; ++// CraftBukkit start ++import java.util.List; ++import javax.annotation.Nullable; ++// CraftBukkit end + + public final class Ingredient implements StackedContents.IngredientInfo>, Predicate { + +@@ -38,7 +42,25 @@ + return recipeitemstack.values; + }); + private final HolderSet values; ++ // CraftBukkit start ++ @Nullable ++ private List itemStacks; + ++ public boolean isExact() { ++ return this.itemStacks != null; ++ } ++ ++ public List itemStacks() { ++ return this.itemStacks; ++ } ++ ++ public static Ingredient ofStacks(List stacks) { ++ Ingredient recipe = Ingredient.of(stacks.stream().map(ItemStack::getItem)); ++ recipe.itemStacks = stacks; ++ return recipe; ++ } ++ // CraftBukkit end ++ + private Ingredient(HolderSet entries) { + entries.unwrap().ifRight((list) -> { + if (list.isEmpty()) { +@@ -70,6 +92,17 @@ + } + + public boolean test(ItemStack itemstack) { ++ // CraftBukkit start ++ if (this.isExact()) { ++ for (ItemStack itemstack1 : this.itemStacks()) { ++ if (itemstack1.getItem() == itemstack.getItem() && ItemStack.isSameItemSameComponents(itemstack, itemstack1)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // CraftBukkit end + return itemstack.is(this.values); + } + +@@ -79,7 +112,7 @@ + + public boolean equals(Object object) { + if (object instanceof Ingredient recipeitemstack) { +- return Objects.equals(this.values, recipeitemstack.values); ++ return Objects.equals(this.values, recipeitemstack.values) && Objects.equals(this.itemStacks, recipeitemstack.itemStacks); // CraftBukkit + } else { + return false; + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch new file mode 100644 index 0000000000..ad97a94316 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch @@ -0,0 +1,9 @@ +--- a/net/minecraft/world/item/crafting/Recipe.java ++++ b/net/minecraft/world/item/crafting/Recipe.java +@@ -44,4 +44,6 @@ + } + + RecipeBookCategory recipeBookCategory(); ++ ++ org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id); // CraftBukkit + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch new file mode 100644 index 0000000000..818a4d266e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/item/crafting/RecipeHolder.java ++++ b/net/minecraft/world/item/crafting/RecipeHolder.java +@@ -5,10 +5,21 @@ + import net.minecraft.network.codec.StreamCodec; + import net.minecraft.resources.ResourceKey; + +-public record RecipeHolder>(ResourceKey> id, T value) { ++// CraftBukkit start ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end + +- public static final StreamCodec> STREAM_CODEC = StreamCodec.composite(ResourceKey.streamCodec(Registries.RECIPE), RecipeHolder::id, Recipe.STREAM_CODEC, RecipeHolder::value, RecipeHolder::new); ++public record RecipeHolder>(ResourceKey> id, T value) { + ++ // CraftBukkit start ++ public final Recipe toBukkitRecipe() { ++ return this.value.toBukkitRecipe(CraftNamespacedKey.fromMinecraft(this.id.location())); ++ } ++ // CraftBukkit end ++ ++ public static final StreamCodec> STREAM_CODEC = StreamCodec.composite(ResourceKey.streamCodec(Registries.RECIPE), RecipeHolder::id, net.minecraft.world.item.crafting.Recipe.STREAM_CODEC, RecipeHolder::value, RecipeHolder::new); ++ + public boolean equals(Object object) { + if (this == object) { + return true; diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch new file mode 100644 index 0000000000..83a2caa10c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch @@ -0,0 +1,111 @@ +--- a/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/net/minecraft/world/item/crafting/RecipeManager.java +@@ -26,11 +26,6 @@ + import net.minecraft.resources.FileToIdConverter; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; +-import net.minecraft.server.level.ServerLevel; +-import net.minecraft.server.packs.resources.ResourceManager; +-import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +-import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +-import net.minecraft.util.profiling.ProfilerFiller; + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.crafting.display.RecipeDisplay; +@@ -39,6 +34,16 @@ + import net.minecraft.world.level.Level; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.Collections; ++import net.minecraft.server.MinecraftServer; ++// CraftBukkit end ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.packs.resources.ResourceManager; ++import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; ++import net.minecraft.server.packs.resources.SimplePreparableReloadListener; ++import net.minecraft.util.profiling.ProfilerFiller; ++ + public class RecipeManager extends SimplePreparableReloadListener implements RecipeAccess { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -111,7 +116,26 @@ + RecipeManager.LOGGER.info("Loaded {} recipes", prepared.values().size()); + } + ++ // CraftBukkit start ++ public void addRecipe(RecipeHolder irecipe) { ++ org.spigotmc.AsyncCatcher.catchOp("Recipe Add"); // Spigot ++ this.recipes.addRecipe(irecipe); ++ this.finalizeRecipeLoading(); ++ } ++ ++ private FeatureFlagSet featureflagset; ++ ++ public void finalizeRecipeLoading() { ++ if (this.featureflagset != null) { ++ this.finalizeRecipeLoading(this.featureflagset); ++ ++ MinecraftServer.getServer().getPlayerList().reloadRecipes(); ++ } ++ } ++ + public void finalizeRecipeLoading(FeatureFlagSet features) { ++ this.featureflagset = features; ++ // CraftBukkit end + List> list = new ArrayList(); + List list1 = RecipeManager.RECIPE_PROPERTY_SETS.entrySet().stream().map((entry) -> { + return new RecipeManager.IngredientCollector((ResourceKey) entry.getKey(), (RecipeManager.IngredientExtractor) entry.getValue()); +@@ -130,7 +154,7 @@ + StonecutterRecipe recipestonecutting = (StonecutterRecipe) irecipe; + + if (RecipeManager.isIngredientEnabled(features, recipestonecutting.input()) && recipestonecutting.resultDisplay().isEnabled(features)) { +- list.add(new SelectableRecipe.SingleInputEntry<>(recipestonecutting.input(), new SelectableRecipe<>(recipestonecutting.resultDisplay(), Optional.of(recipeholder)))); ++ list.add(new SelectableRecipe.SingleInputEntry(recipestonecutting.input(), new SelectableRecipe<>(recipestonecutting.resultDisplay(), Optional.of((RecipeHolder) recipeholder)))); // CraftBukkit - decompile error + } + } + +@@ -172,7 +196,10 @@ + } + + public > Optional> getRecipeFor(RecipeType type, I input, Level world) { +- return this.recipes.getRecipesFor(type, input, world).findFirst(); ++ // CraftBukkit start ++ List> list = this.recipes.getRecipesFor(type, input, world).toList(); ++ return (list.isEmpty()) ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority ++ // CraftBukkit end + } + + public Optional> byKey(ResourceKey> key) { +@@ -183,7 +210,7 @@ + private > RecipeHolder byKeyTyped(RecipeType type, ResourceKey> key) { + RecipeHolder recipeholder = this.recipes.byKey(key); + +- return recipeholder != null && recipeholder.value().getType().equals(type) ? recipeholder : null; ++ return recipeholder != null && recipeholder.value().getType().equals(type) ? (RecipeHolder) recipeholder : null; // CraftBukkit - decompile error + } + + public Map, RecipePropertySet> getSynchronizedItemProperties() { +@@ -231,6 +258,22 @@ + return new RecipeHolder<>(key, irecipe); + } + ++ // CraftBukkit start ++ public boolean removeRecipe(ResourceKey> mcKey) { ++ boolean removed = this.recipes.removeRecipe((ResourceKey>) (ResourceKey) mcKey); // Paper - generic fix ++ if (removed) { ++ this.finalizeRecipeLoading(); ++ } ++ ++ return removed; ++ } ++ ++ public void clearRecipes() { ++ this.recipes = RecipeMap.create(Collections.emptyList()); ++ this.finalizeRecipeLoading(); ++ } ++ // CraftBukkit end ++ + public static > RecipeManager.CachedCheck createCheck(final RecipeType type) { + return new RecipeManager.CachedCheck() { + @Nullable diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch new file mode 100644 index 0000000000..171f3d8e88 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch @@ -0,0 +1,72 @@ +--- a/net/minecraft/world/item/crafting/RecipeMap.java ++++ b/net/minecraft/world/item/crafting/RecipeMap.java +@@ -11,6 +11,10 @@ + import javax.annotation.Nullable; + import net.minecraft.resources.ResourceKey; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import com.google.common.collect.LinkedHashMultimap; ++import com.google.common.collect.Maps; ++// CraftBukkit end + + public class RecipeMap { + +@@ -35,11 +39,56 @@ + com_google_common_collect_immutablemap_builder.put(recipeholder.id(), recipeholder); + } + +- return new RecipeMap(builder.build(), com_google_common_collect_immutablemap_builder.build()); ++ // CraftBukkit start - mutable ++ return new RecipeMap(LinkedHashMultimap.create(builder.build()), Maps.newHashMap(com_google_common_collect_immutablemap_builder.build())); + } + ++ public void addRecipe(RecipeHolder irecipe) { ++ Collection> map = this.byType.get(irecipe.value().getType()); ++ ++ if (this.byKey.containsKey(irecipe.id())) { ++ throw new IllegalStateException("Duplicate recipe ignored with ID " + irecipe.id()); ++ } else { ++ map.add(irecipe); ++ this.byKey.put(irecipe.id(), irecipe); ++ } ++ } ++ ++ // public boolean removeRecipe(ResourceKey> mcKey) { ++ // boolean removed = false; ++ // Iterator> iter = this.byType.values().iterator(); ++ // while (iter.hasNext()) { ++ // RecipeHolder recipe = iter.next(); ++ // if (recipe.id().equals(mcKey)) { ++ // iter.remove(); ++ // removed = true; ++ // } ++ // } ++ // removed |= this.byKey.remove(mcKey) != null; ++ // ++ // return removed; ++ // } ++ // CraftBukkit end ++ ++ ++ // Paper start - replace removeRecipe implementation ++ public boolean removeRecipe(ResourceKey> mcKey) { ++ //noinspection unchecked ++ final RecipeHolder> remove = (RecipeHolder>) this.byKey.remove(mcKey); ++ if (remove == null) { ++ return false; ++ } ++ final Collection>> recipes = this.byType(remove.value().getType()); ++ if (recipes.remove(remove)) { ++ return true; ++ } ++ return false; ++ // Paper end - why are you using a loop??? ++ } ++ // Paper end - replace removeRecipe implementation ++ + public > Collection> byType(RecipeType type) { +- return this.byType.get(type); ++ return (Collection) this.byType.get(type); // CraftBukkit - decompile error + } + + public Collection> values() { diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch new file mode 100644 index 0000000000..05fee5cb09 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch @@ -0,0 +1,86 @@ +--- a/net/minecraft/world/item/crafting/ShapedRecipe.java ++++ b/net/minecraft/world/item/crafting/ShapedRecipe.java +@@ -16,6 +16,13 @@ + import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay; + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; ++import org.bukkit.inventory.RecipeChoice; ++// CraftBukkit end + + public class ShapedRecipe implements CraftingRecipe { + +@@ -39,7 +46,69 @@ + this(group, category, raw, result, true); + } + ++ // CraftBukkit start + @Override ++ public org.bukkit.inventory.ShapedRecipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result); ++ CraftShapedRecipe recipe = new CraftShapedRecipe(id, result, this); ++ recipe.setGroup(this.group); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ switch (this.pattern.height()) { ++ case 1: ++ switch (this.pattern.width()) { ++ case 1: ++ recipe.shape("a"); ++ break; ++ case 2: ++ recipe.shape("ab"); ++ break; ++ case 3: ++ recipe.shape("abc"); ++ break; ++ } ++ break; ++ case 2: ++ switch (this.pattern.width()) { ++ 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.pattern.width()) { ++ 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 (Optional list : this.pattern.ingredients()) { ++ RecipeChoice choice = CraftRecipe.toBukkit(list); ++ if (choice != RecipeChoice.empty()) { // Paper ++ recipe.setIngredient(c, choice); ++ } ++ ++ c++; ++ } ++ return recipe; ++ } ++ // CraftBukkit end ++ ++ @Override + public RecipeSerializer getSerializer() { + return RecipeSerializer.SHAPED_RECIPE; + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch new file mode 100644 index 0000000000..167de63903 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/item/crafting/ShapelessRecipe.java ++++ b/net/minecraft/world/item/crafting/ShapelessRecipe.java +@@ -16,6 +16,12 @@ + import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay; + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; ++// CraftBukkit end + + public class ShapelessRecipe implements CraftingRecipe { + +@@ -33,7 +39,23 @@ + this.ingredients = ingredients; + } + ++ // CraftBukkit start ++ @SuppressWarnings("unchecked") + @Override ++ public org.bukkit.inventory.ShapelessRecipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result); ++ CraftShapelessRecipe recipe = new CraftShapelessRecipe(id, result, this); ++ recipe.setGroup(this.group); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ for (Ingredient list : this.ingredients) { ++ recipe.addIngredient(CraftRecipe.toBukkit(list)); ++ } ++ return recipe; ++ } ++ // CraftBukkit end ++ ++ @Override + public RecipeSerializer getSerializer() { + return RecipeSerializer.SHAPELESS_RECIPE; + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch new file mode 100644 index 0000000000..4b26fa911a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/item/crafting/SmeltingRecipe.java ++++ b/net/minecraft/world/item/crafting/SmeltingRecipe.java +@@ -4,6 +4,14 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class SmeltingRecipe extends AbstractCookingRecipe { + + public SmeltingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) { +@@ -45,4 +53,17 @@ + + return recipebookcategory; + } ++ ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result()); ++ ++ CraftFurnaceRecipe recipe = new CraftFurnaceRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime()); ++ recipe.setGroup(this.group()); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ return recipe; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch new file mode 100644 index 0000000000..173f836106 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java ++++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java +@@ -14,6 +14,14 @@ + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.item.crafting.display.SmithingRecipeDisplay; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftSmithingTransformRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class SmithingTransformRecipe implements SmithingRecipe { + + final Optional template; +@@ -22,8 +30,15 @@ + final ItemStack result; + @Nullable + private PlacementInfo placementInfo; ++ final boolean copyDataComponents; // Paper - Option to prevent data components copy + + public SmithingTransformRecipe(Optional template, Optional base, Optional addition, ItemStack result) { ++ // Paper start - Option to prevent data components copy ++ this(template, base, addition, result, true); ++ } ++ public SmithingTransformRecipe(Optional template, Optional base, Optional addition, ItemStack result, boolean copyDataComponents) { ++ this.copyDataComponents = copyDataComponents; ++ // Paper end - Option to prevent data components copy + this.template = template; + this.base = base; + this.addition = addition; +@@ -33,7 +48,9 @@ + public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) { + ItemStack itemstack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount()); + ++ if (this.copyDataComponents) { // Paper - Option to prevent data components copy + itemstack.applyComponents(this.result.getComponentsPatch()); ++ } // Paper - Option to prevent data components copy + return itemstack; + } + +@@ -71,6 +88,17 @@ + return List.of(new SmithingRecipeDisplay(Ingredient.optionalIngredientToDisplay(this.template), Ingredient.optionalIngredientToDisplay(this.base), Ingredient.optionalIngredientToDisplay(this.addition), new SlotDisplay.ItemStackSlotDisplay(this.result), new SlotDisplay.ItemSlotDisplay(Items.SMITHING_TABLE))); + } + ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result); ++ ++ CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy ++ ++ return recipe; ++ } ++ // CraftBukkit end ++ + public static class Serializer implements RecipeSerializer { + + private static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch new file mode 100644 index 0000000000..a5e660382a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java ++++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java +@@ -21,6 +21,13 @@ + import net.minecraft.world.item.equipment.trim.TrimPattern; + import net.minecraft.world.item.equipment.trim.TrimPatterns; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftSmithingTrimRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class SmithingTrimRecipe implements SmithingRecipe { + + final Optional template; +@@ -28,18 +35,28 @@ + final Optional addition; + @Nullable + private PlacementInfo placementInfo; ++ final boolean copyDataComponents; // Paper - Option to prevent data components copy + + public SmithingTrimRecipe(Optional template, Optional base, Optional addition) { ++ // Paper start - Option to prevent data components copy ++ this(template, base, addition, true); ++ } ++ public SmithingTrimRecipe(Optional template, Optional base, Optional addition, boolean copyDataComponents) { ++ this.copyDataComponents = copyDataComponents; ++ // Paper end - Option to prevent data components copy + this.template = template; + this.base = base; + this.addition = addition; + } + + public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) { +- return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template()); ++ return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template(), this.copyDataComponents); + } + + public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template) { ++ return applyTrim(registries, base, addition, template, true); ++ } ++ public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template, boolean copyDataComponents) { + Optional> optional = TrimMaterials.getFromIngredient(registries, addition); + Optional> optional1 = TrimPatterns.getFromTemplate(registries, template); + +@@ -49,7 +66,7 @@ + if (armortrim != null && armortrim.hasPatternAndMaterial((Holder) optional1.get(), (Holder) optional.get())) { + return ItemStack.EMPTY; + } else { +- ItemStack itemstack3 = base.copyWithCount(1); ++ ItemStack itemstack3 = copyDataComponents ? base.copyWithCount(1) : new ItemStack(base.getItem(), 1); // Paper - Option to prevent data components copy + + itemstack3.set(DataComponents.TRIM, new ArmorTrim((Holder) optional.get(), (Holder) optional1.get())); + return itemstack3; +@@ -97,6 +114,13 @@ + return List.of(new SmithingRecipeDisplay(slotdisplay2, slotdisplay, slotdisplay1, new SlotDisplay.SmithingTrimDemoSlotDisplay(slotdisplay, slotdisplay1, slotdisplay2), new SlotDisplay.ItemSlotDisplay(Items.SMITHING_TABLE))); + } + ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy ++ } ++ // CraftBukkit end ++ + public static class Serializer implements RecipeSerializer { + + private static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch new file mode 100644 index 0000000000..7c099a0fca --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/item/crafting/SmokingRecipe.java ++++ b/net/minecraft/world/item/crafting/SmokingRecipe.java +@@ -4,6 +4,14 @@ + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftSmokingRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class SmokingRecipe extends AbstractCookingRecipe { + + public SmokingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) { +@@ -29,4 +37,17 @@ + public RecipeBookCategory recipeBookCategory() { + return RecipeBookCategories.SMOKER_FOOD; + } ++ ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result()); ++ ++ CraftSmokingRecipe recipe = new CraftSmokingRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime()); ++ recipe.setGroup(this.group()); ++ recipe.setCategory(CraftRecipe.getCategory(this.category())); ++ ++ return recipe; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch new file mode 100644 index 0000000000..fd7dc9145c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/item/crafting/StonecutterRecipe.java ++++ b/net/minecraft/world/item/crafting/StonecutterRecipe.java +@@ -7,6 +7,14 @@ + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.item.crafting.display.StonecutterRecipeDisplay; + ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end ++ + public class StonecutterRecipe extends SingleItemRecipe { + + public StonecutterRecipe(String group, Ingredient ingredient, ItemStack result) { +@@ -36,4 +44,16 @@ + public RecipeBookCategory recipeBookCategory() { + return RecipeBookCategories.STONECUTTER; + } ++ ++ // CraftBukkit start ++ @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result()); ++ ++ CraftStonecuttingRecipe recipe = new CraftStonecuttingRecipe(id, result, CraftRecipe.toBukkit(this.input())); ++ recipe.setGroup(this.group()); ++ ++ return recipe; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch new file mode 100644 index 0000000000..7cc6f6f6d9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/item/crafting/TransmuteRecipe.java ++++ b/net/minecraft/world/item/crafting/TransmuteRecipe.java +@@ -19,6 +19,13 @@ + import net.minecraft.world.item.crafting.display.SlotDisplay; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++// CraftBukkit start ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemType; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.craftbukkit.inventory.CraftTransmuteRecipe; ++import org.bukkit.inventory.Recipe; ++// CraftBukkit end + + public class TransmuteRecipe implements CraftingRecipe { + +@@ -84,7 +91,14 @@ + return List.of(new ShapelessCraftingRecipeDisplay(List.of(this.input.display(), this.material.display()), new SlotDisplay.ItemSlotDisplay(this.result), new SlotDisplay.ItemSlotDisplay(Items.CRAFTING_TABLE))); + } + ++ // CraftBukkit start + @Override ++ public Recipe toBukkitRecipe(NamespacedKey id) { ++ return new CraftTransmuteRecipe(id, CraftItemType.minecraftToBukkit(this.result.value()), CraftRecipe.toBukkit(this.input), CraftRecipe.toBukkit(this.material)); ++ } ++ // CraftBukkit end ++ ++ @Override + public RecipeSerializer getSerializer() { + return RecipeSerializer.TRANSMUTE; + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch new file mode 100644 index 0000000000..519f69ad9b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/world/item/enchantment/ItemEnchantments.java ++++ b/net/minecraft/world/item/enchantment/ItemEnchantments.java +@@ -26,12 +26,25 @@ + import net.minecraft.world.item.Item; + import net.minecraft.world.item.TooltipFlag; + import net.minecraft.world.item.component.TooltipProvider; ++// Paper start ++import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap; ++// Paper end + + public class ItemEnchantments implements TooltipProvider { +- public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntOpenHashMap<>(), true); ++ // Paper start ++ private static final java.util.Comparator> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName); ++ public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); ++ // Paper end + private static final Codec LEVEL_CODEC = Codec.intRange(1, 255); +- private static final Codec>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC) +- .xmap(Object2IntOpenHashMap::new, Function.identity()); ++ private static final Codec>> LEVELS_CODEC = Codec.unboundedMap( ++ Enchantment.CODEC, LEVEL_CODEC ++ )// Paper start - sort enchantments ++ .xmap(m -> { ++ final Object2IntAVLTreeMap> map = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER); ++ map.putAll(m); ++ return map; ++ }, Function.identity()); ++ // Paper end - sort enchantments + private static final Codec FULL_CODEC = RecordCodecBuilder.create( + instance -> instance.group( + LEVELS_CODEC.fieldOf("levels").forGetter(component -> component.enchantments), +@@ -41,16 +54,16 @@ + ); + public static final Codec CODEC = Codec.withAlternative(FULL_CODEC, LEVELS_CODEC, map -> new ItemEnchantments(map, true)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( +- ByteBufCodecs.map(Object2IntOpenHashMap::new, Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT), ++ ByteBufCodecs.map((v) -> new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT), + component -> component.enchantments, + ByteBufCodecs.BOOL, + component -> component.showInTooltip, + ItemEnchantments::new + ); +- final Object2IntOpenHashMap> enchantments; ++ final Object2IntAVLTreeMap> enchantments; // Paper + public final boolean showInTooltip; + +- ItemEnchantments(Object2IntOpenHashMap> enchantments, boolean showInTooltip) { ++ ItemEnchantments(Object2IntAVLTreeMap> enchantments, boolean showInTooltip) { // Paper + this.enchantments = enchantments; + this.showInTooltip = showInTooltip; + +@@ -139,7 +152,7 @@ + } + + public static class Mutable { +- private final Object2IntOpenHashMap> enchantments = new Object2IntOpenHashMap<>(); ++ private final Object2IntAVLTreeMap> enchantments = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER); // Paper + public boolean showInTooltip; + + public Mutable(ItemEnchantments enchantmentsComponent) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch new file mode 100644 index 0000000000..abe8774935 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java ++++ b/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java +@@ -34,7 +34,7 @@ + int j = Math.round(Mth.randomBetween(randomsource, this.minDuration.calculate(level), this.maxDuration.calculate(level)) * 20.0F); + int k = Math.max(0, Math.round(Mth.randomBetween(randomsource, this.minAmplifier.calculate(level), this.maxAmplifier.calculate(level)))); + +- entityliving.addEffect(new MobEffectInstance((Holder) optional.get(), j, k)); ++ entityliving.addEffect(new MobEffectInstance((Holder) optional.get(), j, k), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch new file mode 100644 index 0000000000..711eebddb2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java ++++ b/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java +@@ -21,9 +21,9 @@ + public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) { + ItemStack itemStack = context.itemStack(); + if (itemStack.has(DataComponents.MAX_DAMAGE) && itemStack.has(DataComponents.DAMAGE)) { +- ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; ++ // ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - EntityDamageItemEvent - always pass in entity + int i = (int)this.amount.calculate(level); +- itemStack.hurtAndBreak(i, world, serverPlayer2, context.onBreak()); ++ itemStack.hurtAndBreak(i, world, context.owner(), context.onBreak()); // Paper - EntityDamageItemEvent - always pass in entity + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch new file mode 100644 index 0000000000..80efcc0872 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/item/enchantment/effects/Ignite.java ++++ b/net/minecraft/world/item/enchantment/effects/Ignite.java +@@ -7,6 +7,10 @@ + import net.minecraft.world.item.enchantment.EnchantedItemInUse; + import net.minecraft.world.item.enchantment.LevelBasedValue; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++// CraftBukkit end + + public record Ignite(LevelBasedValue duration) implements EnchantmentEntityEffect { + +@@ -18,7 +22,21 @@ + + @Override + public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) { +- user.igniteForSeconds(this.duration.calculate(level)); ++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item ++ EntityCombustEvent entityCombustEvent; ++ if (context.owner() != null) { ++ entityCombustEvent = new EntityCombustByEntityEvent(context.owner().getBukkitEntity(), user.getBukkitEntity(), this.duration.calculate(level)); ++ } else { ++ entityCombustEvent = new EntityCombustEvent(user.getBukkitEntity(), this.duration.calculate(level)); ++ } ++ ++ org.bukkit.Bukkit.getPluginManager().callEvent(entityCombustEvent); ++ if (entityCombustEvent.isCancelled()) { ++ return; ++ } ++ ++ user.igniteForSeconds(entityCombustEvent.getDuration(), false); ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch new file mode 100644 index 0000000000..78dc09a7de --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java ++++ b/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java +@@ -26,7 +26,7 @@ + + if ((Boolean) this.predicate.map((blockpredicate) -> { + return blockpredicate.test(world, blockposition); +- }).orElse(true) && world.setBlockAndUpdate(blockposition, this.blockState.getState(user.getRandom(), blockposition))) { ++ }).orElse(true) && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, blockposition, this.blockState.getState(user.getRandom(), blockposition), user)) { // CraftBukkit - Call EntityBlockFormEvent + this.triggerGameEvent.ifPresent((holder) -> { + world.gameEvent(user, holder, blockposition); + }); diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch new file mode 100644 index 0000000000..2a0ad9e549 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java ++++ b/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java +@@ -37,7 +37,7 @@ + + if (blockposition1.distToCenterSqr(pos.x(), (double) blockposition1.getY() + 0.5D, pos.z()) < (double) Mth.square(j) && (Boolean) this.predicate.map((blockpredicate) -> { + return blockpredicate.test(world, blockposition1); +- }).orElse(true) && world.setBlockAndUpdate(blockposition1, this.blockState.getState(randomsource, blockposition1))) { ++ }).orElse(true) && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, blockposition1, this.blockState.getState(randomsource, blockposition1), user)) { // CraftBukkit - Call EntityBlockFormEvent for Frost Walker + this.triggerGameEvent.ifPresent((holder) -> { + world.gameEvent(user, holder, blockposition1); + }); diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch new file mode 100644 index 0000000000..8ae0ac6d6e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java ++++ b/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java +@@ -19,6 +19,11 @@ + import net.minecraft.world.item.enchantment.EnchantedItemInUse; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import net.minecraft.world.item.Items; ++import org.bukkit.event.entity.CreatureSpawnEvent; ++import org.bukkit.event.weather.LightningStrikeEvent; ++// CraftBukkit end + + public record SummonEntityEffect(HolderSet> entityTypes, boolean joinTeam) implements EnchantmentEntityEffect { + +@@ -34,7 +39,7 @@ + Optional>> optional = this.entityTypes().getRandomElement(world.getRandom()); + + if (!optional.isEmpty()) { +- Entity entity1 = ((EntityType) ((Holder) optional.get()).value()).spawn(world, blockposition, EntitySpawnReason.TRIGGERED); ++ Entity entity1 = ((EntityType) ((Holder) optional.get()).value()).create(world, null, blockposition, EntitySpawnReason.TRIGGERED, false, false); // CraftBukkit + + if (entity1 != null) { + if (entity1 instanceof LightningBolt) { +@@ -46,6 +51,11 @@ + + entitylightning.setCause(entityplayer); + } ++ // CraftBukkit start ++ world.strikeLightning(entity1, (context.itemStack().getItem() == Items.TRIDENT) ? LightningStrikeEvent.Cause.TRIDENT : LightningStrikeEvent.Cause.ENCHANTMENT); ++ } else { ++ world.addFreshEntityWithPassengers(entity1, CreatureSpawnEvent.SpawnReason.ENCHANTMENT); ++ // CraftBukkit end + } + + if (this.joinTeam && user.getTeam() != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch b/paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch new file mode 100644 index 0000000000..a8626b121e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/item/trading/Merchant.java ++++ b/net/minecraft/world/item/trading/Merchant.java +@@ -20,6 +20,7 @@ + + void overrideOffers(MerchantOffers offers); + ++ default void processTrade(MerchantOffer merchantRecipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper + void notifyTrade(MerchantOffer offer); + + void notifyTradeUpdated(ItemStack stack); +@@ -54,4 +55,6 @@ + boolean isClientSide(); + + boolean stillValid(Player player); ++ ++ org.bukkit.craftbukkit.inventory.CraftMerchant getCraftMerchant(); // CraftBukkit + } diff --git a/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch b/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch new file mode 100644 index 0000000000..bf956ac4ae --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch @@ -0,0 +1,90 @@ +--- a/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/net/minecraft/world/item/trading/MerchantOffer.java +@@ -8,6 +8,8 @@ + import net.minecraft.util.Mth; + import net.minecraft.world.item.ItemStack; + ++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; // CraftBukkit ++ + public class MerchantOffer { + + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { +@@ -31,6 +33,10 @@ + return merchantrecipe.priceMultiplier; + }), Codec.INT.lenientOptionalFieldOf("xp", 1).forGetter((merchantrecipe) -> { + return merchantrecipe.xp; ++ // Paper start ++ }), Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter((merchantrecipe) -> { ++ return merchantrecipe.ignoreDiscounts; ++ // Paper end + })).apply(instance, MerchantOffer::new); + }); + public static final StreamCodec STREAM_CODEC = StreamCodec.of(MerchantOffer::writeToStream, MerchantOffer::createFromStream); +@@ -44,8 +50,22 @@ + public int demand; + public float priceMultiplier; + public int xp; ++ public boolean ignoreDiscounts; // Paper - Add ignore discounts API ++ // CraftBukkit start ++ private CraftMerchantRecipe bukkitHandle; + +- private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience) { ++ public CraftMerchantRecipe asBukkit() { ++ return (this.bukkitHandle == null) ? this.bukkitHandle = new CraftMerchantRecipe(this) : this.bukkitHandle; ++ } ++ ++ public MerchantOffer(ItemCost baseCostA, Optional costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { // Paper ++ this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand); ++ this.ignoreDiscounts = ignoreDiscounts; // Paper ++ this.bukkitHandle = bukkit; ++ } ++ // CraftBukkit end ++ ++ private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience, final boolean ignoreDiscounts) { // Paper + this.baseCostA = firstBuyItem; + this.costB = secondBuyItem; + this.result = sellItem; +@@ -56,6 +76,7 @@ + this.demand = demandBonus; + this.priceMultiplier = priceMultiplier; + this.xp = merchantExperience; ++ this.ignoreDiscounts = ignoreDiscounts; // Paper + } + + public MerchantOffer(ItemCost buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) { +@@ -71,11 +92,11 @@ + } + + public MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) { +- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience); ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience, false); // Paper + } + + private MerchantOffer(MerchantOffer offer) { +- this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp); ++ this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp, offer.ignoreDiscounts); // Paper + } + + public ItemStack getBaseCostA() { +@@ -110,7 +131,7 @@ + } + + public void updateDemand() { +- this.demand = this.demand + this.uses - (this.maxUses - this.uses); ++ this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 + } + + public ItemStack assemble() { +@@ -185,7 +206,11 @@ + if (!this.satisfiedBy(firstBuyStack, secondBuyStack)) { + return false; + } else { +- firstBuyStack.shrink(this.getCostA().getCount()); ++ // CraftBukkit start ++ if (!this.getCostA().isEmpty()) { ++ firstBuyStack.shrink(this.getCostA().getCount()); ++ } ++ // CraftBukkit end + if (!this.getCostB().isEmpty()) { + secondBuyStack.shrink(this.getCostB().getCount()); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch new file mode 100644 index 0000000000..3c091f17a7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/BaseCommandBlock.java ++++ b/net/minecraft/world/level/BaseCommandBlock.java +@@ -33,6 +33,10 @@ + private String command = ""; + @Nullable + private Component customName; ++ // CraftBukkit start ++ @Override ++ public abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper); ++ // CraftBukkit end + + public BaseCommandBlock() {} + +@@ -132,7 +136,7 @@ + + }); + +- minecraftserver.getCommands().performPrefixedCommand(commandlistenerwrapper, this.command); ++ minecraftserver.getCommands().dispatchServerCommand(commandlistenerwrapper, this.command); // CraftBukkit + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Executing command block"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Command to be executed"); +@@ -174,6 +178,7 @@ + @Override + public void sendSystemMessage(Component message) { + if (this.trackOutput) { ++ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks + SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; + Date date = new Date(); + +@@ -200,7 +205,7 @@ + } + + public InteractionResult usedBy(Player player) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + return InteractionResult.PASS; + } else { + if (player.getCommandSenderWorld().isClientSide) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch new file mode 100644 index 0000000000..c836ef12e6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch @@ -0,0 +1,167 @@ +--- a/net/minecraft/world/level/BaseSpawner.java ++++ b/net/minecraft/world/level/BaseSpawner.java +@@ -49,15 +49,17 @@ + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; ++ private int tickDelay = 0; // Paper - Configurable mob spawner tick rate + + public BaseSpawner() {} + + public void setEntityId(EntityType type, @Nullable Level world, RandomSource random, BlockPos pos) { + this.getOrCreateNextSpawnData(world, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString()); ++ this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282 + } + + public boolean isNearPlayer(Level world, BlockPos pos) { +- return world.hasNearbyAlivePlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); ++ return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API + } + + public void clientTick(Level world, BlockPos pos) { +@@ -82,13 +84,19 @@ + } + + public void serverTick(ServerLevel world, BlockPos pos) { ++ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick ++ // Paper start - Configurable mob spawner tick rate ++ if (spawnDelay > 0 && --tickDelay > 0) return; ++ tickDelay = world.paperConfig().tickRates.mobSpawner; ++ if (tickDelay == -1) { return; } // If disabled ++ // Paper end - Configurable mob spawner tick rate + if (this.isNearPlayer(world, pos)) { +- if (this.spawnDelay == -1) { ++ if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate + this.delay(world, pos); + } + + if (this.spawnDelay > 0) { +- --this.spawnDelay; ++ this.spawnDelay -= tickDelay; // Paper - Configurable mob spawner tick rate + } else { + boolean flag = false; + RandomSource randomsource = world.getRandom(); +@@ -125,6 +133,20 @@ + } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) { + continue; + } ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()), ++ io.papermc.paper.util.MCUtil.toLocation(world, pos) ++ ); ++ if (!event.callEvent()) { ++ flag = true; ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ // Paper end - PreCreatureSpawnEvent + + Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, EntitySpawnReason.SPAWNER, (entity1) -> { + entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot()); +@@ -143,6 +165,7 @@ + return; + } + ++ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob entityinsentient = (Mob) entity; +@@ -157,13 +180,27 @@ + ((Mob) entity).finalizeSpawn(world, world.getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.SPAWNER, (SpawnGroupData) null); + } + +- Optional optional1 = mobspawnerdata.getEquipment(); ++ Optional optional1 = mobspawnerdata.getEquipment(); // CraftBukkit - decompile error + + Objects.requireNonNull(entityinsentient); + optional1.ifPresent(entityinsentient::equip); ++ // Spigot Start ++ if ( entityinsentient.level().spigotConfig.nerfSpawnerMobs ) ++ { ++ entityinsentient.aware = false; ++ } ++ // Spigot End + } + +- if (!world.tryAddFreshEntityWithPassengers(entity)) { ++ entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason ++ flag = true; // Paper ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { ++ continue; ++ } ++ if (!world.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) { ++ // CraftBukkit end + this.delay(world, pos); + return; + } +@@ -174,7 +211,7 @@ + ((Mob) entity).spawnAnim(); + } + +- flag = true; ++ //flag = true; // Paper - moved up above cancellable event + } + } + +@@ -202,7 +239,13 @@ + } + + public void load(@Nullable Level world, BlockPos pos, CompoundTag nbt) { ++ // Paper start - use larger int if set ++ if (nbt.contains("Paper.Delay")) { ++ this.spawnDelay = nbt.getInt("Paper.Delay"); ++ } else { + this.spawnDelay = nbt.getShort("Delay"); ++ } ++ // Paper end + boolean flag = nbt.contains("SpawnData", 10); + + if (flag) { +@@ -225,9 +268,15 @@ + this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData()); + } + ++ // Paper start - use ints if set ++ if (nbt.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ this.minSpawnDelay = nbt.getInt("Paper.MinSpawnDelay"); ++ this.maxSpawnDelay = nbt.getInt("Paper.MaxSpawnDelay"); ++ this.spawnCount = nbt.getShort("SpawnCount"); ++ } else // Paper end + if (nbt.contains("MinSpawnDelay", 99)) { +- this.minSpawnDelay = nbt.getShort("MinSpawnDelay"); +- this.maxSpawnDelay = nbt.getShort("MaxSpawnDelay"); ++ this.minSpawnDelay = nbt.getInt("MinSpawnDelay"); // Paper - short -> int ++ this.maxSpawnDelay = nbt.getInt("MaxSpawnDelay"); // Paper - short -> int + this.spawnCount = nbt.getShort("SpawnCount"); + } + +@@ -244,9 +293,20 @@ + } + + public CompoundTag save(CompoundTag nbt) { +- nbt.putShort("Delay", (short) this.spawnDelay); +- nbt.putShort("MinSpawnDelay", (short) this.minSpawnDelay); +- nbt.putShort("MaxSpawnDelay", (short) this.maxSpawnDelay); ++ // Paper start ++ if (spawnDelay > Short.MAX_VALUE) { ++ nbt.putInt("Paper.Delay", this.spawnDelay); ++ } ++ nbt.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay)); ++ ++ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) { ++ nbt.putInt("Paper.MinSpawnDelay", this.minSpawnDelay); ++ nbt.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay); ++ } ++ ++ nbt.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay)); ++ nbt.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay)); ++ // Paper end + nbt.putShort("SpawnCount", (short) this.spawnCount); + nbt.putShort("MaxNearbyEntities", (short) this.maxNearbyEntities); + nbt.putShort("RequiredPlayerRange", (short) this.requiredPlayerRange); diff --git a/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch new file mode 100644 index 0000000000..b3ea51c1e2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch @@ -0,0 +1,90 @@ +--- a/net/minecraft/world/level/BlockGetter.java ++++ b/net/minecraft/world/level/BlockGetter.java +@@ -12,6 +12,7 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.util.Mth; ++import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.BlockEntityType; + import net.minecraft.world.level.block.state.BlockState; +@@ -31,11 +32,20 @@ + default Optional getBlockEntity(BlockPos pos, BlockEntityType type) { + BlockEntity tileentity = this.getBlockEntity(pos); + +- return tileentity != null && tileentity.getType() == type ? Optional.of(tileentity) : Optional.empty(); ++ return tileentity != null && tileentity.getType() == type ? (Optional) Optional.of(tileentity) : Optional.empty(); // CraftBukkit - decompile error + } + + BlockState getBlockState(BlockPos pos); ++ // Paper start - if loaded util ++ @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition); + ++ default @Nullable Block getBlockIfLoaded(BlockPos blockposition) { ++ BlockState type = this.getBlockStateIfLoaded(blockposition); ++ return type == null ? null : type.getBlock(); ++ } ++ @Nullable FluidState getFluidIfLoaded(BlockPos blockposition); ++ // Paper end ++ + FluidState getFluidState(BlockPos pos); + + default int getLightEmission(BlockPos pos) { +@@ -59,10 +69,25 @@ + }); + } + +- default BlockHitResult clip(ClipContext context) { +- return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { +- BlockState iblockdata = this.getBlockState(blockposition); +- FluidState fluid = this.getFluidState(blockposition); ++ // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace ++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(raytrace1, blockposition, null); ++ } ++ ++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing ++ // Paper start - Prevent raytrace from loading chunks ++ BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); ++ if (iblockdata == null) { ++ // copied the last function parameter (listed below) ++ Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); ++ ++ return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); ++ } ++ // Paper end - Prevent raytrace from loading chunks ++ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate ++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); + VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); +@@ -73,6 +98,18 @@ + double d1 = movingobjectpositionblock1 == null ? Double.MAX_VALUE : raytrace1.getFrom().distanceToSqr(movingobjectpositionblock1.getLocation()); + + return d0 <= d1 ? movingobjectpositionblock : movingobjectpositionblock1; ++ } ++ // CraftBukkit end ++ ++ default BlockHitResult clip(ClipContext context) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(context, (java.util.function.Predicate) null); ++ } ++ ++ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing ++ return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { ++ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing + }, (raytrace1) -> { + Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); + +@@ -145,7 +182,7 @@ + double d13 = d10 * (i1 > 0 ? 1.0D - Mth.frac(d4) : Mth.frac(d4)); + double d14 = d11 * (j1 > 0 ? 1.0D - Mth.frac(d5) : Mth.frac(d5)); + +- Object object; ++ T object; // CraftBukkit - decompile error + + do { + if (d12 > 1.0D && d13 > 1.0D && d14 > 1.0D) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch new file mode 100644 index 0000000000..be7a951951 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/ChunkPos.java ++++ b/net/minecraft/world/level/ChunkPos.java +@@ -46,6 +46,7 @@ + public static final int REGION_MAX_INDEX = 31; + public final int x; + public final int z; ++ public final long longKey; // Paper + private static final int HASH_A = 1664525; + private static final int HASH_C = 1013904223; + private static final int HASH_Z_XOR = -559038737; +@@ -53,16 +54,19 @@ + public ChunkPos(int x, int z) { + this.x = x; + this.z = z; ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public ChunkPos(BlockPos pos) { + this.x = SectionPos.blockToSectionCoord(pos.getX()); + this.z = SectionPos.blockToSectionCoord(pos.getZ()); ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public ChunkPos(long pos) { + this.x = (int)pos; + this.z = (int)(pos >> 32); ++ this.longKey = asLong(this.x, this.z); // Paper + } + + public static ChunkPos minFromRegion(int x, int z) { +@@ -74,7 +78,7 @@ + } + + public long toLong() { +- return asLong(this.x, this.z); ++ return longKey; // Paper + } + + public static long asLong(int chunkX, int chunkZ) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch new file mode 100644 index 0000000000..20d8ff1d94 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/ClipContext.java ++++ b/net/minecraft/world/level/ClipContext.java +@@ -22,7 +22,7 @@ + private final CollisionContext collisionContext; + + public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) { +- this(start, end, shapeType, fluidHandling, CollisionContext.of(entity)); ++ this(start, end, shapeType, fluidHandling, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit + } + + public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, CollisionContext shapeContext) { +@@ -79,7 +79,7 @@ + + private final Predicate canPick; + +- private Fluid(final Predicate predicate) { ++ private Fluid(final Predicate predicate) { // CraftBukkit - decompile error + this.canPick = predicate; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch new file mode 100644 index 0000000000..b4743ab862 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/level/EmptyBlockGetter.java ++++ b/net/minecraft/world/level/EmptyBlockGetter.java +@@ -17,7 +17,19 @@ + return null; + } + ++ // Paper start - If loaded util + @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return Fluids.EMPTY.defaultFluidState(); ++ } ++ ++ @Override ++ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ // Paper end ++ ++ @Override + public BlockState getBlockState(BlockPos pos) { + return Blocks.AIR.defaultBlockState(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch new file mode 100644 index 0000000000..09a6133595 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/level/EntityGetter.java ++++ b/net/minecraft/world/level/EntityGetter.java +@@ -71,6 +71,11 @@ + } + } + ++ // Paper start - Affects Spawning API ++ default @Nullable Player findNearbyPlayer(Entity entity, double maxDistance, @Nullable Predicate predicate) { ++ return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate); ++ } ++ // Paper end - Affects Spawning API + @Nullable + default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { + double d = -1.0; +@@ -89,6 +94,28 @@ + return player; + } + ++ // Paper start ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { ++ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? EntitySelector.NO_SPECTATORS : net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR); ++ } ++ ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { ++ com.google.common.collect.ImmutableList.Builder builder = com.google.common.collect.ImmutableList.builder(); ++ ++ for (Player human : this.players()) { ++ if (predicate == null || predicate.test(human)) { ++ double distanceSquared = human.distanceToSqr(x, y, z); ++ ++ if (radius < 0.0D || distanceSquared < radius * radius) { ++ builder.add(human.getBukkitEntity()); ++ } ++ } ++ } ++ ++ return builder.build(); ++ } ++ // Paper end ++ + @Nullable + default Player getNearestPlayer(Entity entity, double maxDistance) { + return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, false); +@@ -100,6 +127,20 @@ + return this.getNearestPlayer(x, y, z, maxDistance, predicate); + } + ++ // Paper start - Affects Spawning API ++ default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) { ++ for (Player player : this.players()) { ++ if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check ++ double distanceSqr = player.distanceToSqr(x, y, z); ++ if (range < 0.0D || distanceSqr < range * range) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ // Paper end - Affects Spawning API ++ + default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { + for (Player player : this.players()) { + if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { +@@ -124,4 +165,11 @@ + + return null; + } ++ ++ // Paper start - check global player list where appropriate ++ @Nullable ++ default Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.getPlayerByUUID(uuid); ++ } ++ // Paper end - check global player list where appropriate + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch new file mode 100644 index 0000000000..13a583766e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch @@ -0,0 +1,321 @@ +--- a/net/minecraft/world/level/GameRules.java ++++ b/net/minecraft/world/level/GameRules.java +@@ -36,6 +36,14 @@ + + public class GameRules { + ++ // Paper start - allow disabling gamerule limits ++ private static final boolean DISABLE_LIMITS = Boolean.getBoolean("paper.disableGameRuleLimits"); ++ ++ private static int limit(final int limit, final int unlimited) { ++ return DISABLE_LIMITS ? unlimited : limit; ++ } ++ // Paper end - allow disabling gamerule limits ++ + public static final int DEFAULT_RANDOM_TICK_SPEED = 3; + static final Logger LOGGER = LogUtils.getLogger(); + private static final Map, GameRules.Type> GAME_RULE_TYPES = Maps.newTreeMap(Comparator.comparing((gamerules_gamerulekey) -> { +@@ -58,7 +66,7 @@ + public static final GameRules.Key RULE_SENDCOMMANDFEEDBACK = GameRules.register("sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_REDUCEDDEBUGINFO = GameRules.register("reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { + int i = gamerules_gameruleboolean.get() ? 22 : 23; +- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); ++ Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +@@ -74,7 +82,7 @@ + public static final GameRules.Key RULE_MAX_ENTITY_CRAMMING = GameRules.register("maxEntityCramming", GameRules.Category.MOBS, GameRules.IntegerValue.create(24)); + public static final GameRules.Key RULE_WEATHER_CYCLE = GameRules.register("doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_LIMITED_CRAFTING = GameRules.register("doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { +- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); ++ Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +@@ -90,7 +98,7 @@ + public static final GameRules.Key RULE_DISABLE_RAIDS = GameRules.register("disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false)); + public static final GameRules.Key RULE_DOINSOMNIA = GameRules.register("doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_DO_IMMEDIATE_RESPAWN = GameRules.register("doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { +- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); ++ Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +@@ -120,15 +128,16 @@ + public static final GameRules.Key RULE_GLOBAL_SOUND_EVENTS = GameRules.register("globalSoundEvents", GameRules.Category.MISC, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_DO_VINES_SPREAD = GameRules.register("doVinesSpread", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_ENDER_PEARLS_VANISH_ON_DEATH = GameRules.register("enderPearlsVanishOnDeath", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true)); +- public static final GameRules.Key RULE_MINECART_MAX_SPEED = GameRules.register("minecartMaxSpeed", GameRules.Category.MISC, GameRules.IntegerValue.create(8, 1, 1000, FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (minecraftserver, gamerules_gameruleint) -> { ++ public static final GameRules.Key RULE_MINECART_MAX_SPEED = GameRules.register("minecartMaxSpeed", GameRules.Category.MISC, GameRules.IntegerValue.create(8, 1, limit(1000, Integer.MAX_VALUE), FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (minecraftserver, gamerules_gameruleint) -> { // Paper - allow disabling gamerule limits + })); +- public static final GameRules.Key RULE_SPAWN_CHUNK_RADIUS = GameRules.register("spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, 32, FeatureFlagSet.of(), (minecraftserver, gamerules_gameruleint) -> { +- ServerLevel worldserver = minecraftserver.overworld(); ++ public static final GameRules.Key RULE_SPAWN_CHUNK_RADIUS = GameRules.register("spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, limit(32, Integer.MAX_VALUE), FeatureFlagSet.of(), (minecraftserver, gamerules_gameruleint) -> { // Paper - allow disabling gamerule limits ++ ServerLevel worldserver = minecraftserver; // CraftBukkit - per-world + + worldserver.setDefaultSpawnPos(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle()); + })); + private final Map, GameRules.Value> rules; + private final FeatureFlagSet enabledFeatures; ++ private final GameRules.Value[] gameruleArray; // Paper - Perf: Use array for gamerule storage + + private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { + GameRules.Key gamerules_gamerulekey = new GameRules.Key<>(name, category); +@@ -161,10 +170,21 @@ + private GameRules(Map, GameRules.Value> rules, FeatureFlagSet enabledFeatures) { + this.rules = rules; + this.enabledFeatures = enabledFeatures; ++ ++ // Paper start - Perf: Use array for gamerule storage ++ int arraySize = GameRules.Key.lastGameRuleIndex + 1; ++ GameRules.Value[] values = new GameRules.Value[arraySize]; ++ ++ for (Entry, GameRules.Value> entry : rules.entrySet()) { ++ values[entry.getKey().gameRuleIndex] = entry.getValue(); ++ } ++ ++ this.gameruleArray = values; ++ // Paper end - Perf: Use array for gamerule storage + } + + public > T getRule(GameRules.Key key) { +- T t0 = (GameRules.Value) this.rules.get(key); ++ T t0 = key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage + + if (t0 == null) { + throw new IllegalArgumentException("Tried to access invalid game rule"); +@@ -184,7 +204,7 @@ + + private void loadFromTag(DynamicLike values) { + this.rules.forEach((gamerules_gamerulekey, gamerules_gamerulevalue) -> { +- DataResult dataresult = values.get(gamerules_gamerulekey.id).asString(); ++ DataResult dataresult = values.get(gamerules_gamerulekey.id).asString(); // CraftBukkit - decompile error + + Objects.requireNonNull(gamerules_gamerulevalue); + dataresult.ifSuccess(gamerules_gamerulevalue::deserialize); +@@ -205,22 +225,22 @@ + + private > void callVisitorCap(GameRules.GameRuleTypeVisitor visitor, GameRules.Key key, GameRules.Type type) { + if (type.requiredFeatures.isSubsetOf(this.enabledFeatures)) { +- visitor.visit(key, type); +- type.callVisitor(visitor, key); ++ visitor.visit((GameRules.Key) key, (GameRules.Type) type); // CraftBukkit - decompile error ++ ((GameRules.Type) type).callVisitor(visitor, (GameRules.Key) key); // CraftBukkit - decompile error + } + + } + +- public void assignFrom(GameRules rules, @Nullable MinecraftServer server) { +- rules.rules.keySet().forEach((gamerules_gamerulekey) -> { +- this.assignCap(gamerules_gamerulekey, rules, server); ++ public void assignFrom(GameRules gamerules, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ gamerules.rules.keySet().forEach((gamerules_gamerulekey) -> { ++ this.assignCap(gamerules_gamerulekey, gamerules, minecraftserver); + }); + } + +- private > void assignCap(GameRules.Key key, GameRules rules, @Nullable MinecraftServer server) { +- T t0 = rules.getRule(key); ++ private > void assignCap(GameRules.Key gamerules_gamerulekey, GameRules gamerules, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ T t0 = gamerules.getRule(gamerules_gamerulekey); + +- this.getRule(key).setFrom(t0, server); ++ this.getRule(gamerules_gamerulekey).setFrom(t0, minecraftserver); + } + + public boolean getBoolean(GameRules.Key rule) { +@@ -232,6 +252,10 @@ + } + + public static final class Key> { ++ // Paper start - Perf: Use array for gamerule storage ++ public static int lastGameRuleIndex = 0; ++ public final int gameRuleIndex = lastGameRuleIndex++; ++ // Paper end - Perf: Use array for gamerule storage + + final String id; + private final GameRules.Category category; +@@ -285,11 +309,11 @@ + + final Supplier> argument; + private final Function, T> constructor; +- final BiConsumer callback; ++ final BiConsumer callback; // CraftBukkit - per-world + private final GameRules.VisitorCaller visitorCaller; + final FeatureFlagSet requiredFeatures; + +- Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor, FeatureFlagSet requiredFeatures) { ++ Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor, FeatureFlagSet requiredFeatures) { // CraftBukkit - per-world + this.argument = argumentType; + this.constructor = ruleFactory; + this.callback = changeCallback; +@@ -302,7 +326,7 @@ + } + + public T createRule() { +- return (GameRules.Value) this.constructor.apply(this); ++ return this.constructor.apply(this); // CraftBukkit - decompile error + } + + public void callVisitor(GameRules.GameRuleTypeVisitor consumer, GameRules.Key key) { +@@ -322,21 +346,21 @@ + this.type = type; + } + +- protected abstract void updateFromArgument(CommandContext context, String name); ++ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - Add WorldGameRuleChangeEvent + +- public void setFromArgument(CommandContext context, String name) { +- this.updateFromArgument(context, name); +- this.onChanged(((CommandSourceStack) context.getSource()).getServer()); ++ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent ++ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent ++ this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world + } + +- public void onChanged(@Nullable MinecraftServer server) { +- if (server != null) { +- this.type.callback.accept(server, this.getSelf()); ++ public void onChanged(@Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ if (minecraftserver != null) { ++ this.type.callback.accept(minecraftserver, this.getSelf()); + } + + } + +- protected abstract void deserialize(String value); ++ public abstract void deserialize(String value); // PAIL - private->public + + public abstract String serialize(); + +@@ -350,7 +374,7 @@ + + protected abstract T copy(); + +- public abstract void setFrom(T rule, @Nullable MinecraftServer server); ++ public abstract void setFrom(T t0, @Nullable ServerLevel minecraftserver); // CraftBukkit - per-world + } + + public interface GameRuleTypeVisitor { +@@ -366,7 +390,7 @@ + + private boolean value; + +- static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { ++ static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { // CraftBukkit - per-world + return new GameRules.Type<>(BoolArgumentType::bool, (gamerules_gameruledefinition) -> { + return new GameRules.BooleanValue(gamerules_gameruledefinition, initialValue); + }, changeCallback, GameRules.GameRuleTypeVisitor::visitBoolean, FeatureFlagSet.of()); +@@ -383,17 +407,20 @@ + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = BoolArgumentType.getBool(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Boolean.parseBoolean(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public boolean get() { + return this.value; + } + +- public void set(boolean value, @Nullable MinecraftServer server) { +- this.value = value; +- this.onChanged(server); ++ public void set(boolean flag, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ this.value = flag; ++ this.onChanged(minecraftserver); + } + + @Override +@@ -402,7 +429,7 @@ + } + + @Override +- protected void deserialize(String value) { ++ public void deserialize(String value) { // PAIL - protected->public + this.value = Boolean.parseBoolean(value); + } + +@@ -421,9 +448,9 @@ + return new GameRules.BooleanValue(this.type, this.value); + } + +- public void setFrom(GameRules.BooleanValue rule, @Nullable MinecraftServer server) { +- this.value = rule.value; +- this.onChanged(server); ++ public void setFrom(GameRules.BooleanValue gamerules_gameruleboolean, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ this.value = gamerules_gameruleboolean.value; ++ this.onChanged(minecraftserver); + } + } + +@@ -431,13 +458,13 @@ + + private int value; + +- private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { ++ private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { // CraftBukkit - per-world + return new GameRules.Type<>(IntegerArgumentType::integer, (gamerules_gameruledefinition) -> { + return new GameRules.IntegerValue(gamerules_gameruledefinition, initialValue); + }, changeCallback, GameRules.GameRuleTypeVisitor::visitInteger, FeatureFlagSet.of()); + } + +- static GameRules.Type create(int initialValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer changeCallback) { ++ static GameRules.Type create(int initialValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer changeCallback) { // CraftBukkit - per-world + return new GameRules.Type<>(() -> { + return IntegerArgumentType.integer(min, max); + }, (gamerules_gameruledefinition) -> { +@@ -456,17 +483,20 @@ + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = IntegerArgumentType.getInteger(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Integer.parseInt(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public int get() { + return this.value; + } + +- public void set(int value, @Nullable MinecraftServer server) { +- this.value = value; +- this.onChanged(server); ++ public void set(int i, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ this.value = i; ++ this.onChanged(minecraftserver); + } + + @Override +@@ -475,7 +505,7 @@ + } + + @Override +- protected void deserialize(String value) { ++ public void deserialize(String value) { // PAIL - protected->public + this.value = IntegerValue.safeParse(value); + } + +@@ -517,9 +547,9 @@ + return new GameRules.IntegerValue(this.type, this.value); + } + +- public void setFrom(GameRules.IntegerValue rule, @Nullable MinecraftServer server) { +- this.value = rule.value; +- this.onChanged(server); ++ public void setFrom(GameRules.IntegerValue gamerules_gameruleint, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world ++ this.value = gamerules_gameruleint.value; ++ this.onChanged(minecraftserver); + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch new file mode 100644 index 0000000000..6a3ac30d97 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch @@ -0,0 +1,710 @@ +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -25,8 +25,10 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import io.papermc.paper.util.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -43,6 +45,7 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.alchemy.PotionBrewing; +@@ -57,12 +60,14 @@ + import net.minecraft.world.level.block.entity.FuelValues; + import net.minecraft.world.level.block.entity.TickingBlockEntity; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.border.BorderChangeListener; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.ChunkSource; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.level.dimension.DimensionType; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.entity.EntityTypeTest; + import net.minecraft.world.level.entity.LevelEntityGetter; + import net.minecraft.world.level.gameevent.GameEvent; +@@ -81,6 +86,25 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.Scoreboard; + ++// CraftBukkit start ++import java.util.HashMap; ++import java.util.Map; ++import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket; ++import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket; ++import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket; ++import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket; ++import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.block.CapturedBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.craftbukkit.util.CraftSpawnCategory; ++import org.bukkit.entity.SpawnCategory; ++import org.bukkit.event.block.BlockPhysicsEvent; ++// CraftBukkit end ++ + public abstract class Level implements LevelAccessor, AutoCloseable { + + public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); +@@ -94,7 +118,7 @@ + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- protected final List blockEntityTickers = Lists.newArrayList(); ++ public final List blockEntityTickers = Lists.newArrayList(); // Paper - public + protected final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); + private boolean tickingBlockEntities; +@@ -121,23 +145,91 @@ + private final DamageSources damageSources; + private long subTickCount; + +- protected Level(WritableLevelData properties, ResourceKey registryRef, RegistryAccess registryManager, Holder dimensionEntry, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) { +- this.levelData = properties; +- this.dimensionTypeRegistration = dimensionEntry; +- final DimensionType dimensionmanager = (DimensionType) dimensionEntry.value(); ++ // CraftBukkit start Added the following ++ private final CraftWorld world; ++ public boolean pvpMode; ++ public org.bukkit.generator.ChunkGenerator generator; + +- this.dimension = registryRef; +- this.isClientSide = isClient; ++ public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710 ++ public boolean captureBlockStates = false; ++ public boolean captureTreeGeneration = false; ++ public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper ++ public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates ++ public List captureDrops; ++ public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); ++ public boolean populating; ++ public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot ++ // Paper start - add paper world config ++ private final io.papermc.paper.configuration.WorldConfiguration paperConfig; ++ public io.papermc.paper.configuration.WorldConfiguration paperConfig() { ++ return this.paperConfig; ++ } ++ // Paper end - add paper world config ++ ++ public static BlockPos lastPhysicsProblem; // Spigot ++ private org.spigotmc.TickLimiter entityLimiter; ++ private org.spigotmc.TickLimiter tileLimiter; ++ private int tileTickPosition; ++ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions ++ public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here ++ ++ public CraftWorld getWorld() { ++ return this.world; ++ } ++ ++ public CraftServer getCraftServer() { ++ return (CraftServer) Bukkit.getServer(); ++ } ++ // Paper start - Use getChunkIfLoadedImmediately ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkIfLoaded(chunkX, chunkZ) != null; ++ } ++ // Paper end - Use getChunkIfLoadedImmediately ++ // Paper start - per world ticks per spawn ++ private int getTicksPerSpawn(SpawnCategory spawnCategory) { ++ final int perWorld = this.paperConfig().entities.spawning.ticksPerSpawn.getInt(CraftSpawnCategory.toNMS(spawnCategory)); ++ if (perWorld >= 0) { ++ return perWorld; ++ } ++ return this.getCraftServer().getTicksPerSpawns(spawnCategory); ++ } ++ // Paper end ++ ++ ++ public abstract ResourceKey getTypeKey(); ++ ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config ++ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot ++ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config ++ this.generator = gen; ++ this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); ++ ++ // CraftBukkit Ticks things ++ for (SpawnCategory spawnCategory : SpawnCategory.values()) { ++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory)); // Paper ++ } ++ } ++ ++ // CraftBukkit end ++ this.levelData = worlddatamutable; ++ this.dimensionTypeRegistration = holder; ++ final DimensionType dimensionmanager = (DimensionType) holder.value(); ++ ++ this.dimension = resourcekey; ++ this.isClientSide = flag; + if (dimensionmanager.coordinateScale() != 1.0D) { +- this.worldBorder = new WorldBorder(this) { ++ this.worldBorder = new WorldBorder() { // CraftBukkit - decompile error + @Override + public double getCenterX() { +- return super.getCenterX() / dimensionmanager.coordinateScale(); ++ return super.getCenterX(); // CraftBukkit + } + + @Override + public double getCenterZ() { +- return super.getCenterZ() / dimensionmanager.coordinateScale(); ++ return super.getCenterZ(); // CraftBukkit + } + }; + } else { +@@ -145,13 +237,90 @@ + } + + this.thread = Thread.currentThread(); +- this.biomeManager = new BiomeManager(this, seed); +- this.isDebug = debugWorld; +- this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates); +- this.registryAccess = registryManager; +- this.damageSources = new DamageSources(registryManager); ++ this.biomeManager = new BiomeManager(this, i); ++ this.isDebug = flag1; ++ this.neighborUpdater = new CollectingNeighborUpdater(this, j); ++ this.registryAccess = iregistrycustom; ++ this.damageSources = new DamageSources(iregistrycustom); ++ // CraftBukkit start ++ this.getWorldBorder().world = (ServerLevel) this; ++ // From PlayerList.setPlayerFileData ++ this.getWorldBorder().addListener(new BorderChangeListener() { ++ @Override ++ public void onBorderSizeSet(WorldBorder border, double size) { ++ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); ++ } ++ ++ @Override ++ public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) { ++ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); ++ } ++ ++ @Override ++ public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) { ++ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); ++ } ++ ++ @Override ++ public void onBorderSetWarningTime(WorldBorder border, int warningTime) { ++ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); ++ } ++ ++ @Override ++ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) { ++ Level.this.getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); ++ } ++ ++ @Override ++ public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {} ++ ++ @Override ++ public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {} ++ }); ++ // CraftBukkit end ++ this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); ++ this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + } + ++ // Paper start - Cancel hit for vanished players ++ // ret true if no collision ++ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision, ++ BlockPos position, boolean checkCanSee) { ++ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) ++ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ()); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ List entities = this.getEntities(null, voxelshape.bounds()); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity = entities.get(i); ++ ++ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer ++ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) { ++ continue; ++ } ++ ++ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); ++ // elide the last check since vanilla calls with entity = null ++ // only we care about the source for the canSee check ++ if (entity.isRemoved() || !entity.blocksBuilding) { ++ continue; ++ } ++ ++ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - Cancel hit for vanished players + @Override + public boolean isClientSide() { + return this.isClientSide; +@@ -163,6 +332,13 @@ + return null; + } + ++ // Paper start ++ public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) { ++ // To be patched over ++ return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType(); ++ } ++ // Paper end ++ + public boolean isInWorldBounds(BlockPos pos) { + return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos); + } +@@ -172,25 +348,87 @@ + } + + private static boolean isInWorldBoundsHorizontal(BlockPos pos) { +- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; ++ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() + } + + private static boolean isOutsideSpawnableHeight(int y) { + return y < -20000000 || y >= 20000000; + } + +- public LevelChunk getChunkAt(BlockPos pos) { ++ public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline + return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); + } + + @Override +- public LevelChunk getChunk(int chunkX, int chunkZ) { +- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL); ++ public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline ++ // Paper start - Perf: make sure loaded chunks get the inlined variant of this function ++ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); ++ LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump ++ // Paper end - Perf: make sure loaded chunks get the inlined variant of this function + } + ++ // Paper start - if loaded + @Nullable + @Override ++ public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) { ++ return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z); ++ } ++ ++ @Override ++ @Nullable ++ public final BlockState getBlockStateIfLoaded(BlockPos pos) { ++ // CraftBukkit start - tree generation ++ if (this.captureTreeGeneration) { ++ CraftBlockState previous = this.capturedBlockStates.get(pos); ++ if (previous != null) { ++ return previous.getHandle(); ++ } ++ } ++ // CraftBukkit end ++ if (this.isOutsideBuildHeight(pos)) { ++ return Blocks.VOID_AIR.defaultBlockState(); ++ } else { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getBlockState(pos); ++ } ++ } ++ ++ @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ ++ return chunk == null ? null : chunk.getFluidState(blockposition); ++ } ++ ++ @Override ++ public final boolean hasChunkAt(BlockPos pos) { ++ return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper - Perf: Optimize Level.hasChunkAt(BlockPosition)Z ++ } ++ ++ public final boolean isLoadedAndInBounds(BlockPos blockposition) { // Paper - final for inline ++ return getWorldBorder().isWithinBounds(blockposition) && getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; ++ } ++ ++ public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { // Overridden in WorldServer for ABI compat which has final ++ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ } ++ public final @Nullable LevelChunk getChunkIfLoaded(BlockPos blockposition) { ++ return ((ServerLevel) this).getChunkSource().getChunkAtIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ } ++ ++ // reduces need to do isLoaded before getType ++ public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos blockposition) { ++ return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null; ++ } ++ ++ @Override + public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { ++ // Paper end + ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); + + if (ichunkaccess == null && create) { +@@ -207,6 +445,22 @@ + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { ++ // CraftBukkit start - tree generation ++ if (this.captureTreeGeneration) { ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed ++ CraftBlockState blockstate = this.capturedBlockStates.get(pos); ++ if (blockstate == null) { ++ blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); ++ this.capturedBlockStates.put(pos.immutable(), blockstate); ++ } ++ blockstate.setData(state); ++ blockstate.setFlag(flags); ++ return true; ++ } ++ // CraftBukkit end + if (this.isOutsideBuildHeight(pos)) { + return false; + } else if (!this.isClientSide && this.isDebug()) { +@@ -214,44 +468,125 @@ + } else { + LevelChunk chunk = this.getChunkAt(pos); + Block block = state.getBlock(); +- BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0); + ++ // CraftBukkit start - capture blockstates ++ boolean captured = false; ++ if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) { ++ CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot ++ blockstate.setFlag(flags); // Paper - set flag ++ this.capturedBlockStates.put(pos.immutable(), blockstate); ++ captured = true; ++ } ++ // CraftBukkit end ++ ++ BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag ++ + if (iblockdata1 == null) { ++ // CraftBukkit start - remove blockstate if failed (or the same) ++ if (this.captureBlockStates && captured) { ++ this.capturedBlockStates.remove(pos); ++ } ++ // CraftBukkit end + return false; + } else { + BlockState iblockdata2 = this.getBlockState(pos); + +- if (iblockdata2 == state) { ++ /* ++ if (iblockdata2 == iblockdata) { + if (iblockdata1 != iblockdata2) { +- this.setBlocksDirty(pos, iblockdata1, iblockdata2); ++ this.setBlocksDirty(blockposition, iblockdata1, iblockdata2); + } + +- if ((flags & 2) != 0 && (!this.isClientSide || (flags & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { +- this.sendBlockUpdated(pos, iblockdata1, state, flags); ++ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { ++ this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); + } + +- if ((flags & 1) != 0) { +- this.blockUpdated(pos, iblockdata1.getBlock()); +- if (!this.isClientSide && state.hasAnalogOutputSignal()) { +- this.updateNeighbourForOutputSignal(pos, block); ++ if ((i & 1) != 0) { ++ this.blockUpdated(blockposition, iblockdata1.getBlock()); ++ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) { ++ this.updateNeighbourForOutputSignal(blockposition, block); + } + } + +- if ((flags & 16) == 0 && maxUpdateDepth > 0) { +- int k = flags & -34; ++ if ((i & 16) == 0 && j > 0) { ++ int k = i & -34; + +- iblockdata1.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1); +- state.updateNeighbourShapes(this, pos, k, maxUpdateDepth - 1); +- state.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1); ++ iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); ++ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); ++ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); + } + +- this.onBlockStateChange(pos, iblockdata1, iblockdata2); ++ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); + } ++ */ + ++ // CraftBukkit start ++ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates ++ // Modularize client and physic updates ++ // Spigot start ++ try { ++ this.notifyAndUpdatePhysics(pos, chunk, iblockdata1, state, iblockdata2, flags, maxUpdateDepth); ++ } catch (StackOverflowError ex) { ++ Level.lastPhysicsProblem = new BlockPos(pos); ++ } ++ // Spigot end ++ } ++ // CraftBukkit end ++ + return true; ++ } ++ } ++ } ++ ++ // CraftBukkit start - Split off from above in order to directly send client and physic updates ++ public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, BlockState actualBlock, int i, int j) { ++ BlockState iblockdata = newBlock; ++ BlockState iblockdata1 = oldBlock; ++ BlockState iblockdata2 = actualBlock; ++ if (iblockdata2 == iblockdata) { ++ if (iblockdata1 != iblockdata2) { ++ this.setBlocksDirty(blockposition, iblockdata1, iblockdata2); ++ } ++ ++ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement ++ this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); ++ } ++ ++ if ((i & 1) != 0) { ++ this.blockUpdated(blockposition, iblockdata1.getBlock()); ++ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) { ++ this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock()); ++ } ++ } ++ ++ if ((i & 16) == 0 && j > 0) { ++ int k = i & -34; ++ ++ // CraftBukkit start ++ iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam ++ CraftWorld world = ((ServerLevel) this).getWorld(); ++ boolean cancelledUpdates = false; // Paper - Fix block place logic ++ if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent ++ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); ++ this.getCraftServer().getPluginManager().callEvent(event); ++ ++ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic ++ } ++ // CraftBukkit end ++ if (!cancelledUpdates) { // Paper - Fix block place logic ++ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); ++ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); ++ } // Paper - Fix block place logic + } ++ ++ // CraftBukkit start - SPIGOT-5710 ++ if (!this.preventPoiUpdated) { ++ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); ++ } ++ // CraftBukkit end + } + } ++ // CraftBukkit end + + public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {} + +@@ -270,15 +605,33 @@ + return false; + } else { + FluidState fluid = this.getFluidState(pos); ++ // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar ++ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, ++ // it doesn't imply destruction of a block that plays a sound effect / drops an item. ++ boolean playEffect = true; ++ BlockState effectType = iblockdata; ++ int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true); ++ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop); ++ if (!event.callEvent()) { ++ return false; ++ } ++ effectType = ((CraftBlockData) event.getEffectBlock()).getState(); ++ playEffect = event.playEffect(); ++ drop = event.willDrop(); ++ xp = event.getExpToDrop(); ++ } ++ // Paper end - BlockDestroyEvent + +- if (!(iblockdata.getBlock() instanceof BaseFireBlock)) { +- this.levelEvent(2001, pos, Block.getId(iblockdata)); ++ if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent ++ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent + } + + if (drop) { + BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; + +- Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY); ++ Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping ++ iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount + } + + boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth); +@@ -340,10 +693,18 @@ + + @Override + public BlockState getBlockState(BlockPos pos) { ++ // CraftBukkit start - tree generation ++ if (this.captureTreeGeneration) { ++ CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper ++ if (previous != null) { ++ return previous.getHandle(); ++ } ++ } ++ // CraftBukkit end + if (this.isOutsideBuildHeight(pos)) { + return Blocks.VOID_AIR.defaultBlockState(); + } else { +- LevelChunk chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); ++ ChunkAccess chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine + + return chunk.getBlockState(pos); + } +@@ -446,34 +807,53 @@ + this.pendingBlockEntityTickers.clear(); + } + +- Iterator iterator = this.blockEntityTickers.iterator(); ++ // Spigot start ++ // Iterator iterator = this.blockEntityTickers.iterator(); + boolean flag = this.tickRateManager().runsNormally(); + +- while (iterator.hasNext()) { +- TickingBlockEntity tickingblockentity = (TickingBlockEntity) iterator.next(); ++ int tilesThisCycle = 0; ++ var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll ++ toRemove.add(null); // Paper - Fix MC-117075 ++ for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters ++ this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; ++ TickingBlockEntity tickingblockentity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition); ++ // Spigot end + + if (tickingblockentity.isRemoved()) { +- iterator.remove(); ++ // Spigot start ++ tilesThisCycle--; ++ toRemove.add(tickingblockentity); // Paper - Fix MC-117075; use removeAll ++ // Spigot end + } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) { + tickingblockentity.tick(); + } + } ++ this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 + + this.tickingBlockEntities = false; + gameprofilerfiller.pop(); ++ this.spigotConfig.currentPrimedTnt = 0; // Spigot + } + + public void guardEntityTick(Consumer tickConsumer, T entity) { + try { + tickConsumer.accept(entity); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked"); +- +- entity.fillCrashReportCategory(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent block entity and entity crashes ++ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); ++ MinecraftServer.LOGGER.error(msg, throwable); ++ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ // Paper end - Prevent block entity and entity crashes + } ++ } ++ // Paper start - Option to prevent armor stands from doing entity lookups ++ @Override ++ public boolean noCollision(@Nullable Entity entity, AABB box) { ++ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; ++ return LevelAccessor.super.noCollision(entity, box); + } ++ // Paper end - Option to prevent armor stands from doing entity lookups + + public boolean shouldTickDeath(Entity entity) { + return true; +@@ -510,13 +890,32 @@ + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos pos) { +- return this.isOutsideBuildHeight(pos) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE)); ++ // CraftBukkit start ++ return this.getBlockEntity(pos, true); + } + ++ @Nullable ++ public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ net.minecraft.world.level.block.entity.BlockEntity blockEntity; ++ if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) { ++ return blockEntity; ++ } ++ // Paper end - Perf: Optimize capturedTileEntities lookup ++ // CraftBukkit end ++ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); ++ } ++ + public void setBlockEntity(BlockEntity blockEntity) { + BlockPos blockposition = blockEntity.getBlockPos(); + + if (!this.isOutsideBuildHeight(blockposition)) { ++ // CraftBukkit start ++ if (this.captureBlockStates) { ++ this.capturedTileEntities.put(blockposition.immutable(), blockEntity); ++ return; ++ } ++ // CraftBukkit end + this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity); + } + } +@@ -643,7 +1042,7 @@ + + for (int k = 0; k < j; ++k) { + EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; +- T t0 = (Entity) filter.tryCast(entitycomplexpart); ++ T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error + + if (t0 != null && predicate.test(t0)) { + result.add(t0); +@@ -912,7 +1311,7 @@ + + public static enum ExplosionInteraction implements StringRepresentable { + +- NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger"); ++ NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger"), STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY + + public static final Codec CODEC = StringRepresentable.fromEnum(Level.ExplosionInteraction::values); + private final String id; diff --git a/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch new file mode 100644 index 0000000000..7c0828c3ad --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch @@ -0,0 +1,9 @@ +--- a/net/minecraft/world/level/LevelAccessor.java ++++ b/net/minecraft/world/level/LevelAccessor.java +@@ -101,4 +101,6 @@ + default void gameEvent(ResourceKey event, BlockPos pos, GameEvent.Context emitter) { + this.gameEvent((Holder) this.registryAccess().lookupOrThrow(Registries.GAME_EVENT).getOrThrow(event), pos, emitter); + } ++ ++ net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch new file mode 100644 index 0000000000..4d6ea6f76d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/LevelReader.java ++++ b/net/minecraft/world/level/LevelReader.java +@@ -26,6 +26,9 @@ + @Nullable + ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); + ++ @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) ++ @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);} ++ + @Deprecated + boolean hasChunk(int chunkX, int chunkZ); + diff --git a/paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch new file mode 100644 index 0000000000..3008822ea1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/LevelWriter.java ++++ b/net/minecraft/world/level/LevelWriter.java +@@ -28,4 +28,10 @@ + default boolean addFreshEntity(Entity entity) { + return false; + } ++ ++ // CraftBukkit start ++ default boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ return false; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch new file mode 100644 index 0000000000..28fc9a7cb8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch @@ -0,0 +1,212 @@ +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -47,8 +47,13 @@ + import net.minecraft.world.level.levelgen.structure.Structure; + import net.minecraft.world.level.levelgen.structure.structures.NetherFortressStructure; + import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; ++import org.bukkit.craftbukkit.util.CraftSpawnCategory; ++import org.bukkit.entity.SpawnCategory; ++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; ++// CraftBukkit end + + public final class NaturalSpawner { + +@@ -82,6 +87,13 @@ + MobCategory enumcreaturetype = entity.getType().getCategory(); + + if (enumcreaturetype != MobCategory.MISC) { ++ // Paper start - Only count natural spawns ++ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end - Only count natural spawns + BlockPos blockposition = entity.blockPosition(); + + chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> { +@@ -107,15 +119,31 @@ + return (Biome) chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value(); + } + +- public static List getFilteredSpawningCategories(NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rare) { ++ // CraftBukkit start - add server ++ public static List getFilteredSpawningCategories(NaturalSpawner.SpawnState spawnercreature_d, boolean flag, boolean flag1, boolean flag2, ServerLevel worldserver) { ++ LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate ++ // CraftBukkit end + List list = new ArrayList(NaturalSpawner.SPAWNING_CATEGORIES.length); + MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES; + int i = aenumcreaturetype.length; + + for (int j = 0; j < i; ++j) { + MobCategory enumcreaturetype = aenumcreaturetype[j]; ++ // CraftBukkit start - Use per-world spawn limits ++ boolean spawnThisTick = true; ++ int limit = enumcreaturetype.getMaxInstancesPerChunk(); ++ SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype); ++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0; ++ limit = worldserver.getWorld().getSpawnLimit(spawnCategory); ++ } + +- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rare || !enumcreaturetype.isPersistent()) && info.canSpawnForCategoryGlobal(enumcreaturetype)) { ++ if (!spawnThisTick || limit == 0) { ++ continue; ++ } ++ ++ if ((flag || !enumcreaturetype.isFriendly()) && (flag1 || enumcreaturetype.isFriendly()) && (flag2 || !enumcreaturetype.isPersistent()) && spawnercreature_d.canSpawnForCategoryGlobal(enumcreaturetype, limit)) { ++ // CraftBukkit end + list.add(enumcreaturetype); + } + } +@@ -144,6 +172,16 @@ + gameprofilerfiller.pop(); + } + ++ // Paper start - Add mobcaps commands ++ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { ++ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); ++ if (categoryLimit < 1) { ++ return categoryLimit; ++ } ++ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ } ++ // Paper end - Add mobcaps commands ++ + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { + BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); + +@@ -164,9 +202,9 @@ + StructureManager structuremanager = world.structureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); +- BlockState iblockdata = chunk.getBlockState(pos); ++ BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn + +- if (!iblockdata.isRedstoneConductor(chunk, pos)) { ++ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + int j = 0; + int k = 0; +@@ -195,7 +233,7 @@ + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); + +- if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { ++ if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { + Optional optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition); + +@@ -207,7 +245,13 @@ + j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount); + } + +- if (NaturalSpawner.isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ // Paper start - PreCreatureSpawnEvent ++ PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ if (doSpawning == PreSpawnStatus.ABORT) { ++ return; ++ } ++ if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ // Paper end - PreCreatureSpawnEvent + Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); + + if (entityinsentient == null) { +@@ -217,10 +261,15 @@ + entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); + if (NaturalSpawner.isValidPositionForMob(world, entityinsentient, d2)) { + groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.NATURAL, groupdataentity); +- ++j; +- ++k1; +- world.addFreshEntityWithPassengers(entityinsentient); +- runner.run(entityinsentient, chunk); ++ // CraftBukkit start ++ // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs. ++ world.addFreshEntityWithPassengers(entityinsentient, (entityinsentient instanceof net.minecraft.world.entity.animal.Ocelot && !((org.bukkit.entity.Ageable) entityinsentient.getBukkitEntity()).isAdult()) ? SpawnReason.OCELOT_BABY : SpawnReason.NATURAL); ++ if (!entityinsentient.isRemoved()) { ++ ++j; ++ ++k1; ++ runner.run(entityinsentient, chunk); ++ } ++ // CraftBukkit end + if (j >= entityinsentient.getMaxSpawnClusterSize()) { + return; + } +@@ -250,10 +299,31 @@ + return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); + } + +- private static boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { ++ // Paper start - PreCreatureSpawnEvent ++ private enum PreSpawnStatus { ++ FAIL, ++ SUCCESS, ++ CANCELLED, ++ ABORT ++ } ++ private static PreSpawnStatus isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { ++ // Paper end - PreCreatureSpawnEvent + EntityType entitytypes = spawnEntry.type; + +- return entitytypes.getCategory() == MobCategory.MISC ? false : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? false : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)))) : false)); ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(world, pos), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes), SpawnReason.NATURAL ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ return PreSpawnStatus.ABORT; ++ } ++ return PreSpawnStatus.CANCELLED; ++ } ++ // Paper end - PreCreatureSpawnEvent ++ ++ return entitytypes.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? PreSpawnStatus.FAIL : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent + } + + @Nullable +@@ -268,6 +338,7 @@ + NaturalSpawner.LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(type)); + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + } + + return null; +@@ -356,6 +427,7 @@ + entity = biomesettingsmobs_c.type.create(world.getLevel(), EntitySpawnReason.NATURAL); + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + continue; + } + +@@ -369,7 +441,7 @@ + + if (entityinsentient.checkSpawnRules(world, EntitySpawnReason.CHUNK_GENERATION) && entityinsentient.checkSpawnObstruction(world)) { + groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupdataentity); +- world.addFreshEntityWithPassengers(entityinsentient); ++ world.addFreshEntityWithPassengers(entityinsentient, SpawnReason.CHUNK_GEN); // CraftBukkit + flag = true; + } + } +@@ -482,10 +554,12 @@ + return this.unmodifiableMobCategoryCounts; + } + +- boolean canSpawnForCategoryGlobal(MobCategory group) { +- int i = group.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ // CraftBukkit start ++ boolean canSpawnForCategoryGlobal(MobCategory enumcreaturetype, int limit) { ++ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ // CraftBukkit end + +- return this.mobCategoryCounts.getInt(group) < i; ++ return this.mobCategoryCounts.getInt(enumcreaturetype) < i; + } + + boolean canSpawnForCategoryLocal(MobCategory group, ChunkPos chunkPos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch new file mode 100644 index 0000000000..085fb1983b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/level/PathNavigationRegion.java ++++ b/net/minecraft/world/level/PathNavigationRegion.java +@@ -8,6 +8,7 @@ + import net.minecraft.core.Holder; + import net.minecraft.core.SectionPos; + import net.minecraft.core.registries.Registries; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.biome.Biome; + import net.minecraft.world.level.biome.Biomes; +@@ -66,7 +67,7 @@ + private ChunkAccess getChunk(int chunkX, int chunkZ) { + int i = chunkX - this.centerX; + int j = chunkZ - this.centerZ; +- if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) { ++ if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below + ChunkAccess chunkAccess = this.chunks[i][j]; + return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), this.plains.get())); + } else { +@@ -74,7 +75,31 @@ + } + } + ++ // Paper start - if loaded util ++ private @Nullable ChunkAccess getChunkIfLoaded(int x, int z) { ++ // Based on getChunk(int, int) ++ int xx = x - this.centerX; ++ int zz = z - this.centerZ; ++ ++ if (xx >= 0 && xx < this.chunks.length && zz >= 0 && zz < this.chunks[xx].length) { ++ return this.chunks[xx][zz]; ++ } ++ return null; ++ } + @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getFluidState(blockposition); ++ } ++ ++ @Override ++ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) { ++ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ return chunk == null ? null : chunk.getBlockState(blockposition); ++ } ++ // Paper end ++ ++ @Override + public WorldBorder getWorldBorder() { + return this.level.getWorldBorder(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch new file mode 100644 index 0000000000..e0f12942e5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch @@ -0,0 +1,342 @@ +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -22,18 +22,27 @@ + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.boss.EnderDragonPart; ++import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.BaseFireBlock; + import net.minecraft.world.level.block.Block; +-import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.entity.EntityExplodeEvent; ++import org.bukkit.Location; ++import org.bukkit.event.block.BlockExplodeEvent; ++// CraftBukkit end + + public class ServerExplosion implements Explosion { + +@@ -50,16 +59,22 @@ + private final DamageSource damageSource; + private final ExplosionDamageCalculator damageCalculator; + private final Map hitPlayers = new HashMap(); ++ // CraftBukkit - add field ++ public boolean wasCanceled = false; ++ public float yield; ++ // CraftBukkit end ++ public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source + + public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { + this.level = world; + this.source = entity; +- this.radius = power; ++ this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values + this.center = pos; + this.fire = createFire; + this.blockInteraction = destructionType; + this.damageSource = damageSource == null ? world.damageSources().explosion(this) : damageSource; + this.damageCalculator = behavior == null ? this.makeDamageCalculator(entity) : behavior; ++ this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit + } + + private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { +@@ -135,7 +150,8 @@ + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = BlockPos.containing(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); +- FluidState fluid = this.level.getFluidState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed ++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions + + if (!this.level.isInWorldBounds(blockposition)) { + break; +@@ -149,6 +165,15 @@ + + if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); ++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); ++ } ++ } ++ // Paper end - prevent headless pistons from forming + } + + d4 += d0 * 0.30000001192092896D; +@@ -171,7 +196,7 @@ + int l = Mth.floor(this.center.y + (double) f + 1.0D); + int i1 = Mth.floor(this.center.z - (double) f - 1.0D); + int j1 = Mth.floor(this.center.z + (double) f + 1.0D); +- List list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1)); ++ List list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +@@ -192,10 +217,38 @@ + d3 /= d4; + boolean flag = this.damageCalculator.shouldDamageEntity(this, entity); + float f1 = this.damageCalculator.getKnockbackMultiplier(entity); +- float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity); ++ float f2 = !flag && f1 == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions + + if (flag) { +- entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2)); ++ // CraftBukkit start ++ ++ // Special case ender dragon only give knockback if no damage is cancelled ++ // Thinks to note: ++ // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed) ++ // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon ++ // - Damaging EntityEnderDragon does nothing ++ // - EntityEnderDragon hitbock always covers the other parts and is therefore always present ++ if (entity instanceof EnderDragonPart) { ++ continue; ++ } ++ ++ entity.lastDamageCancelled = false; ++ ++ if (entity instanceof EnderDragon) { ++ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) { ++ // Calculate damage separately for each EntityComplexPart ++ if (list.contains(entityComplexPart)) { ++ entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2)); ++ } ++ } ++ } else { ++ entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2)); ++ } ++ ++ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled ++ continue; ++ } ++ // CraftBukkit end + } + + double d5 = (1.0D - d0) * (double) f2 * (double) f1; +@@ -204,7 +257,7 @@ + if (entity instanceof LivingEntity) { + LivingEntity entityliving = (LivingEntity) entity; + +- d6 = d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); ++ d6 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper + } else { + d6 = d5; + } +@@ -214,11 +267,19 @@ + d3 *= d6; + Vec3 vec3d = new Vec3(d1, d2, d3); + ++ // CraftBukkit start - Call EntityKnockbackEvent ++ if (entity instanceof LivingEntity) { ++ // Paper start - knockback events ++ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d6, vec3d); ++ vec3d = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback()); ++ // Paper end - knockback events ++ } ++ // CraftBukkit end + entity.push(vec3d); + if (entity instanceof Player) { + Player entityhuman = (Player) entity; + +- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) { ++ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback + this.hitPlayers.put(entityhuman, vec3d); + } + } +@@ -235,10 +296,62 @@ + List list1 = new ArrayList(); + + Util.shuffle(positions, this.level.random); ++ // CraftBukkit start ++ org.bukkit.World bworld = this.level.getWorld(); ++ Location location = CraftLocation.toBukkit(this.center, bworld); ++ ++ List blockList = new ObjectArrayList<>(); ++ for (int i1 = positions.size() - 1; i1 >= 0; i1--) { ++ BlockPos cpos = positions.get(i1); ++ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ()); ++ if (!bblock.getType().isAir()) { ++ blockList.add(bblock); ++ } ++ } ++ ++ List bukkitBlocks; ++ ++ if (this.source != null) { ++ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blockList, this.yield, this.getBlockInteraction()); ++ this.wasCanceled = event.isCancelled(); ++ bukkitBlocks = event.blockList(); ++ this.yield = event.getYield(); ++ } else { ++ org.bukkit.block.Block block = location.getBlock(); ++ org.bukkit.block.BlockState blockState = (this.damageSource.getDirectBlockState() != null) ? this.damageSource.getDirectBlockState() : block.getState(); ++ BlockExplodeEvent event = CraftEventFactory.callBlockExplodeEvent(block, blockState, blockList, this.yield, this.getBlockInteraction()); ++ this.wasCanceled = event.isCancelled(); ++ bukkitBlocks = event.blockList(); ++ this.yield = event.getYield(); ++ } ++ ++ positions.clear(); ++ ++ for (org.bukkit.block.Block bblock : bukkitBlocks) { ++ BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ()); ++ positions.add(coords); ++ } ++ ++ if (this.wasCanceled) { ++ return; ++ } ++ // CraftBukkit end + Iterator iterator = positions.iterator(); + + while (iterator.hasNext()) { + BlockPos blockposition = (BlockPos) iterator.next(); ++ // CraftBukkit start - TNTPrimeEvent ++ BlockState iblockdata = this.level.getBlockState(blockposition); ++ Block block = iblockdata.getBlock(); ++ if (block instanceof net.minecraft.world.level.block.TntBlock) { ++ Entity sourceEntity = this.source == null ? null : this.source; ++ BlockPos sourceBlock = sourceEntity == null ? BlockPos.containing(this.center) : null; ++ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) { ++ this.level.sendBlockUpdated(blockposition, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client ++ continue; ++ } ++ } ++ // CraftBukkit end + + this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> { + ServerExplosion.addOrAppendStack(list1, itemstack, blockposition1); +@@ -262,13 +375,22 @@ + BlockPos blockposition = (BlockPos) iterator.next(); + + if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockposition).isAir() && this.level.getBlockState(blockposition.below()).isSolidRender()) { +- this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition)); ++ // CraftBukkit start - Ignition by explosion ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockposition, this).isCancelled()) { ++ this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition)); ++ } ++ // CraftBukkit end + } + } + + } + + public void explode() { ++ // CraftBukkit start ++ if (this.radius < 0.1F) { ++ return; ++ } ++ // CraftBukkit end + this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center); + List list = this.calculateExplodedPositions(); + +@@ -288,6 +410,7 @@ + } + + private static void addOrAppendStack(List droppedItemsOut, ItemStack item, BlockPos pos) { ++ if (item.isEmpty()) return; // CraftBukkit - SPIGOT-5425 + Iterator iterator = droppedItemsOut.iterator(); + + do { +@@ -372,4 +495,85 @@ + + } + } ++ ++ // Paper start - Optimize explosions ++ private float getBlockDensity(Vec3 vec3d, Entity entity) { ++ if (!this.level.paperConfig().environment.optimizeExplosions) { ++ return getSeenPercent(vec3d, entity); ++ } ++ CacheKey key = new CacheKey(this, entity.getBoundingBox()); ++ Float blockDensity = this.level.explosionDensityCache.get(key); ++ if (blockDensity == null) { ++ blockDensity = getSeenPercent(vec3d, entity); ++ this.level.explosionDensityCache.put(key, blockDensity); ++ } ++ ++ return blockDensity; ++ } ++ ++ static class CacheKey { ++ private final Level world; ++ private final double posX, posY, posZ; ++ private final double minX, minY, minZ; ++ private final double maxX, maxY, maxZ; ++ ++ public CacheKey(Explosion explosion, AABB aabb) { ++ this.world = explosion.level(); ++ this.posX = explosion.center().x; ++ this.posY = explosion.center().y; ++ this.posZ = explosion.center().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/paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch new file mode 100644 index 0000000000..2c482ebee0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/ServerLevelAccessor.java ++++ b/net/minecraft/world/level/ServerLevelAccessor.java +@@ -8,6 +8,17 @@ + ServerLevel getLevel(); + + default void addFreshEntityWithPassengers(Entity entity) { +- entity.getSelfAndPassengers().forEach(this::addFreshEntity); ++ // CraftBukkit start ++ this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); + } ++ ++ default void addFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ entity.getSelfAndPassengers().forEach((e) -> this.addFreshEntity(e, reason)); ++ } ++ ++ @Override ++ default ServerLevel getMinecraftWorld() { ++ return this.getLevel(); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch new file mode 100644 index 0000000000..8a15d2f4f1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/level/StructureManager.java ++++ b/net/minecraft/world/level/StructureManager.java +@@ -48,7 +48,12 @@ + } + + public List startsForStructure(ChunkPos pos, Predicate predicate) { +- Map map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper start - Fix swamp hut cat generation deadlock ++ return this.startsForStructure(pos, predicate, null); ++ } ++ public List startsForStructure(ChunkPos pos, Predicate predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ Map map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper end - Fix swamp hut cat generation deadlock + Builder builder = ImmutableList.builder(); + + for (Entry entry : map.entrySet()) { +@@ -116,10 +121,20 @@ + } + + public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate) { ++ // Paper start - Fix swamp hut cat generation deadlock ++ return this.getStructureWithPieceAt(pos, predicate, null); ++ } ++ ++ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey tag, @Nullable ServerLevelAccessor levelAccessor) { ++ return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor); ++ } ++ ++ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ // Paper end - Fix swamp hut cat generation deadlock + Registry registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE); + + for (StructureStart structureStart : this.startsForStructure( +- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false) ++ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock + )) { + if (this.structureHasPieceAt(pos, structureStart)) { + return structureStart; diff --git a/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch new file mode 100644 index 0000000000..d1fb7a4640 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -75,8 +75,40 @@ + } + + public static class Builder { ++ // Paper start - Perf: keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it ++ public static class MobList extends java.util.ArrayList { ++ java.util.Set biomes = new java.util.HashSet<>(); ++ ++ @Override ++ public boolean contains(Object o) { ++ return biomes.contains(o); ++ } ++ ++ @Override ++ public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { ++ biomes.add(BiomeSettingsMobs); ++ return super.add(BiomeSettingsMobs); ++ } ++ ++ @Override ++ public MobSpawnSettings.SpawnerData remove(int index) { ++ MobSpawnSettings.SpawnerData removed = super.remove(index); ++ if (removed != null) { ++ biomes.remove(removed); ++ } ++ return removed; ++ } ++ ++ @Override ++ public void clear() { ++ biomes.clear(); ++ super.clear(); ++ } ++ } ++ // use toImmutableEnumMap collector + private final Map> spawners = Stream.of(MobCategory.values()) +- .collect(ImmutableMap.toImmutableMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> Lists.newArrayList())); ++ .collect(Maps.toImmutableEnumMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> new MobList())); // Use MobList instead of ArrayList ++ // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls + private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); + private float creatureGenerationProbability = 0.1F; + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch new file mode 100644 index 0000000000..2c42f4cb51 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/AbstractCandleBlock.java ++++ b/net/minecraft/world/level/block/AbstractCandleBlock.java +@@ -47,6 +47,11 @@ + @Override + protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { + if (!world.isClientSide && projectile.isOnFire() && this.canBeLit(state)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, hit.getBlockPos(), projectile).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + AbstractCandleBlock.setLit(world, state, hit.getBlockPos(), true); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch new file mode 100644 index 0000000000..e2c8cb7f03 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/AbstractCauldronBlock.java ++++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java +@@ -56,7 +56,7 @@ + @Override + protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem()); +- return cauldronInteraction.interact(state, world, pos, player, hand, stack); ++ return cauldronInteraction.interact(state, world, pos, player, hand, stack, hit.getDirection()); // Paper - pass hit direction + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch new file mode 100644 index 0000000000..81462ca698 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/AnvilBlock.java ++++ b/net/minecraft/world/level/block/AnvilBlock.java +@@ -62,8 +62,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_ANVIL); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch new file mode 100644 index 0000000000..88344a70cd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/BambooSaplingBlock.java ++++ b/net/minecraft/world/level/block/BambooSaplingBlock.java +@@ -45,7 +45,7 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if (random.nextInt(3) == 0 && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { ++ if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution + this.growBamboo(world, pos); + } + +@@ -87,6 +87,6 @@ + } + + protected void growBamboo(Level world, BlockPos pos) { +- world.setBlock(pos.above(), (BlockState) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); // CraftBukkit - BlockSpreadEvent + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch new file mode 100644 index 0000000000..eb12b201ec --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch @@ -0,0 +1,89 @@ +--- a/net/minecraft/world/level/block/BambooStalkBlock.java ++++ b/net/minecraft/world/level/block/BambooStalkBlock.java +@@ -134,10 +134,10 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if ((Integer) state.getValue(BambooStalkBlock.STAGE) == 0) { +- if (random.nextInt(3) == 0 && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { ++ if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution + int i = this.getHeightBelowUpToMax(world, pos) + 1; + +- if (i < 16) { ++ if (i < world.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height + this.growBamboo(state, world, pos, random, i); + } + } +@@ -164,7 +164,7 @@ + int i = this.getHeightAboveUpToMax(world, pos); + int j = this.getHeightBelowUpToMax(world, pos); + +- return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; ++ return i + j + 1 < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height + } + + @Override +@@ -183,7 +183,7 @@ + BlockPos blockposition1 = pos.above(i); + BlockState iblockdata1 = world.getBlockState(blockposition1); + +- if (k >= 16 || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { ++ if (k >= world.paperConfig().maxGrowthHeight.bamboo.max || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height + return; + } + +@@ -204,14 +204,18 @@ + BlockPos blockposition1 = pos.below(2); + BlockState iblockdata2 = world.getBlockState(blockposition1); + BambooLeaves blockpropertybamboosize = BambooLeaves.NONE; ++ boolean shouldUpdateOthers = false; // CraftBukkit + + if (height >= 1) { + if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) { + if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) { + blockpropertybamboosize = BambooLeaves.LARGE; + if (iblockdata2.is(Blocks.BAMBOO)) { +- world.setBlock(pos.below(), (BlockState) iblockdata1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); +- world.setBlock(blockposition1, (BlockState) iblockdata2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3); ++ // CraftBukkit start - moved down ++ // world.setBlock(blockposition.below(), (IBlockData) iblockdata1.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.SMALL), 3); ++ // world.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.NONE), 3); ++ shouldUpdateOthers = true; ++ // CraftBukkit end + } + } + } else { +@@ -220,15 +224,22 @@ + } + + int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1; +- int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1; ++ int k = (height < world.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && height != (world.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height + +- world.setBlock(pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) { ++ if (shouldUpdateOthers) { ++ world.setBlock(pos.below(), (BlockState) iblockdata1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); ++ world.setBlock(blockposition1, (BlockState) iblockdata2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3); ++ } ++ } ++ // CraftBukkit end + } + + protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height + ; + } + +@@ -238,7 +249,7 @@ + protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height + ; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch new file mode 100644 index 0000000000..349f801388 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/BarrelBlock.java ++++ b/net/minecraft/world/level/block/BarrelBlock.java +@@ -41,8 +41,7 @@ + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { +- if (world instanceof ServerLevel serverLevel && world.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity) { +- player.openMenu(barrelBlockEntity); ++ if (world instanceof ServerLevel serverLevel && world.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity && player.openMenu(barrelBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.OPEN_BARREL); + PiglinAi.angerNearbyPiglins(serverLevel, player, true); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch new file mode 100644 index 0000000000..aeae088ca7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch @@ -0,0 +1,85 @@ +--- a/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/net/minecraft/world/level/block/BaseFireBlock.java +@@ -12,6 +12,7 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockBehaviour; +@@ -127,6 +128,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!entity.fireImmune()) { + if (entity.getRemainingFireTicks() < 0) { + entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); +@@ -137,7 +139,18 @@ + } + + if (entity.getRemainingFireTicks() >= 0) { +- entity.igniteForSeconds(8.0F); ++ // CraftBukkit start ++ org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), entity.getBukkitEntity(), 8.0F); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ entity.igniteForSeconds(event.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ entity.setRemainingFireTicks(entity.getRemainingFireTicks() - 1); ++ // Paper end - fix EntityCombustEvent cancellation ++ } ++ // CraftBukkit end + } + } + +@@ -146,26 +159,26 @@ + } + + @Override +- protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- if (!oldState.is(state.getBlock())) { ++ protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) { // CraftBukkit - context ++ if (!iblockdata1.is(iblockdata.getBlock())) { + if (BaseFireBlock.inPortalDimension(world)) { +- Optional optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X); ++ Optional optional = PortalShape.findEmptyPortalShape(world, blockposition, Direction.Axis.X); + + if (optional.isPresent()) { +- ((PortalShape) optional.get()).createPortalBlocks(world); ++ ((PortalShape) optional.get()).createPortalBlocks(world, (context == null) ? null : context.getPlayer()); // CraftBukkit - player + return; + } + } + +- if (!state.canSurvive(world, pos)) { +- world.removeBlock(pos, false); ++ if (!iblockdata.canSurvive(world, blockposition)) { ++ this.fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke + } + + } + } + + private static boolean inPortalDimension(Level world) { +- return world.dimension() == Level.OVERWORLD || world.dimension() == Level.NETHER; ++ return world.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD || world.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit - getTypeKey() + } + + @Override +@@ -213,4 +226,12 @@ + } + } + } ++ ++ // CraftBukkit start ++ protected void fireExtinguished(net.minecraft.world.level.LevelAccessor world, BlockPos position) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, position, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ world.removeBlock(position, false); ++ } ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch new file mode 100644 index 0000000000..65470ccca0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch @@ -0,0 +1,56 @@ +--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -22,6 +22,7 @@ + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + + public abstract class BasePressurePlateBlock extends Block { + +@@ -76,6 +77,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + int i = this.getSignalForState(state); + +@@ -91,6 +93,19 @@ + boolean flag = output > 0; + boolean flag1 = j > 0; + ++ // CraftBukkit start - Interact Pressure Plate ++ org.bukkit.World bworld = world.getWorld(); ++ org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager(); ++ ++ if (flag != flag1) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), output, j); ++ manager.callEvent(eventRedstone); ++ ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ j = eventRedstone.getNewCurrent(); ++ } ++ // CraftBukkit end ++ + if (output != j) { + BlockState iblockdata1 = this.setSignalForState(state, j); + +@@ -145,9 +160,15 @@ + } + + protected static int getEntityCount(Level world, AABB box, Class entityClass) { +- return world.getEntitiesOfClass(entityClass, box, EntitySelector.NO_SPECTATORS.and((entity) -> { ++ // CraftBukkit start ++ return BasePressurePlateBlock.getEntities(world, box, entityClass).size(); ++ } ++ ++ protected static java.util.List getEntities(Level world, AABB axisalignedbb, Class oclass) { ++ // CraftBukkit end ++ return world.getEntitiesOfClass(oclass, axisalignedbb, EntitySelector.NO_SPECTATORS.and((entity) -> { + return !entity.isIgnoringBlockTriggers(); +- })).size(); ++ })); // CraftBukkit + } + + protected abstract int getSignalStrength(Level world, BlockPos pos); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch new file mode 100644 index 0000000000..9e6c76abd9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/BaseRailBlock.java ++++ b/net/minecraft/world/level/block/BaseRailBlock.java +@@ -71,6 +71,7 @@ + state = this.updateDir(world, pos, state, true); + if (this.isStraight) { + world.neighborChanged(state, pos, this, null, notify); ++ state = world.getBlockState(pos); // Paper - Fix some rails connecting improperly + } + + return state; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch new file mode 100644 index 0000000000..b23b55da23 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/BeaconBlock.java ++++ b/net/minecraft/world/level/block/BeaconBlock.java +@@ -46,8 +46,7 @@ + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { +- if (!world.isClientSide && world.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity) { +- player.openMenu(beaconBlockEntity); ++ if (!world.isClientSide && world.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity && player.openMenu(beaconBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_BEACON); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch new file mode 100644 index 0000000000..09946b2d9f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -0,0 +1,92 @@ +--- a/net/minecraft/world/level/block/BedBlock.java ++++ b/net/minecraft/world/level/block/BedBlock.java +@@ -95,7 +95,8 @@ + } + } + +- if (!BedBlock.canSetSpawn(world)) { ++ // CraftBukkit - moved world and biome check into EntityHuman ++ if (false && !BedBlock.canSetSpawn(world)) { + world.removeBlock(pos, false); + BlockPos blockposition1 = pos.relative(((Direction) state.getValue(BedBlock.FACING)).getOpposite()); + +@@ -108,25 +109,65 @@ + world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); + return InteractionResult.SUCCESS_SERVER; + } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { ++ if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first + if (!this.kickVillagerOutOfBed(world, pos)) { + player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); + } + + return InteractionResult.SUCCESS_SERVER; + } else { ++ // CraftBukkit start ++ BlockState finaliblockdata = state; ++ BlockPos finalblockposition = pos; ++ // CraftBukkit end + player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { ++ // Paper start - PlayerBedFailEnterEvent ++ if (entityhuman_enumbedresult != null) { ++ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end - PlayerBedFailEnterEvent ++ // CraftBukkit start - handling bed explosion from below here ++ if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent ++ this.explodeBed(finaliblockdata, world, finalblockposition); ++ } else ++ // CraftBukkit end + if (entityhuman_enumbedresult.getMessage() != null) { +- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); ++ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent ++ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent + } ++ } // Paper - PlayerBedFailEnterEvent + + }); + return InteractionResult.SUCCESS_SERVER; ++ } ++ } ++ } ++ ++ // CraftBukkit start ++ private InteractionResult explodeBed(BlockState iblockdata, Level world, BlockPos blockposition) { ++ { ++ { ++ org.bukkit.block.BlockState blockState = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition).getState(); // CraftBukkit - capture BlockState before remove block ++ world.removeBlock(blockposition, false); ++ BlockPos blockposition1 = blockposition.relative(((Direction) iblockdata.getValue(BedBlock.FACING)).getOpposite()); ++ ++ if (world.getBlockState(blockposition1).getBlock() == this) { ++ world.removeBlock(blockposition1, false); ++ } ++ ++ Vec3 vec3d = blockposition.getCenter(); ++ ++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state ++ return InteractionResult.SUCCESS; + } + } + } ++ // CraftBukkit end + + public static boolean canSetSpawn(Level world) { +- return world.dimensionType().bedWorks(); ++ return world.dimensionType().bedWorks(); // Paper - actually check if the bed works + } + + private boolean kickVillagerOutOfBed(Level world, BlockPos pos) { +@@ -320,6 +361,11 @@ + BlockPos blockposition1 = pos.relative((Direction) state.getValue(BedBlock.FACING)); + + world.setBlock(blockposition1, (BlockState) state.setValue(BedBlock.PART, BedPart.HEAD), 3); ++ // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states ++ if (world.captureBlockStates) { ++ return; ++ } ++ // CraftBukkit end + world.blockUpdated(pos, Blocks.AIR); + state.updateNeighbourShapes(world, pos, 3); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch new file mode 100644 index 0000000000..73f015dbe6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch @@ -0,0 +1,79 @@ +--- a/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/net/minecraft/world/level/block/BeehiveBlock.java +@@ -94,8 +94,8 @@ + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity tileentitybeehive) { + if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_BEE_SPAWNS_WHEN_MINING)) { + tileentitybeehive.emptyAllLivingFromHive(player, state, BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY); +@@ -103,7 +103,7 @@ + this.angerNearbyBees(world, pos); + } + +- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); ++ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped + } + + } +@@ -133,7 +133,7 @@ + if (entitybee.getTarget() == null) { + Player entityhuman = (Player) Util.getRandom(list1, world.random); + +- entitybee.setTarget(entityhuman); ++ entitybee.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit + } + } + } +@@ -141,7 +141,7 @@ + } + + public static void dropHoneycomb(Level world, BlockPos pos) { +- popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); ++ popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - Add PlayerShearBlockEvent; conflict on change, item needs to be set below + } + + @Override +@@ -153,8 +153,19 @@ + Item item = stack.getItem(); + + if (stack.is(Items.SHEARS)) { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.BLOCKS, 1.0F, 1.0F); +- BeehiveBlock.dropHoneycomb(world, pos); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) { ++ popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop)); ++ } ++ // Paper end - Add PlayerShearBlockEvent + stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); + flag = true; + world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); +@@ -297,7 +308,7 @@ + ItemStack itemstack = new ItemStack(this); + + itemstack.applyComponents(tileentitybeehive.collectComponents()); +- itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, (Comparable) i)); ++ itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, i)); // CraftBukkit - decompile error + ItemEntity entityitem = new ItemEntity(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack); + + entityitem.setDefaultPickUpDelay(); +@@ -332,7 +343,7 @@ + ItemStack itemstack = super.getCloneItemStack(world, pos, state, includeData); + + if (includeData) { +- itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, (Comparable) ((Integer) state.getValue(BeehiveBlock.HONEY_LEVEL)))); ++ itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, ((Integer) state.getValue(BeehiveBlock.HONEY_LEVEL)))); // CraftBukkit - decompile error + } + + return itemstack; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch new file mode 100644 index 0000000000..d677bbedab --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/BellBlock.java ++++ b/net/minecraft/world/level/block/BellBlock.java +@@ -148,6 +148,11 @@ + if (direction == null) { + direction = (Direction) world.getBlockState(pos).getValue(BellBlock.FACING); + } ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBellRingEvent(world, pos, direction, entity)) { ++ return false; ++ } ++ // CraftBukkit end + + ((BellBlockEntity) tileentity).onHit(direction); + world.playSound((Player) null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch new file mode 100644 index 0000000000..c0eab8e717 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch @@ -0,0 +1,116 @@ +--- a/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -44,6 +44,10 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityInteractEvent; ++// CraftBukkit end + + public class BigDripleafBlock extends HorizontalDirectionalBlock implements BonemealableBlock, SimpleWaterloggedBlock { + +@@ -119,7 +123,7 @@ + + @Override + protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { +- this.setTiltAndScheduleTick(state, world, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN); ++ this.setTiltAndScheduleTick(state, world, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, projectile); // CraftBukkit + } + + @Override +@@ -176,9 +180,23 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { +- this.setTiltAndScheduleTick(state, world, pos, Tilt.UNSTABLE, (SoundEvent) null); ++ // CraftBukkit start - tilt dripleaf ++ org.bukkit.event.Cancellable cancellable; ++ if (entity instanceof Player) { ++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ if (cancellable.isCancelled()) { ++ return; ++ } ++ this.setTiltAndScheduleTick(state, world, pos, Tilt.UNSTABLE, (SoundEvent) null, entity); ++ // CraftBukkit end + } + + } +@@ -192,9 +210,9 @@ + Tilt tilt = (Tilt) state.getValue(BigDripleafBlock.TILT); + + if (tilt == Tilt.UNSTABLE) { +- this.setTiltAndScheduleTick(state, world, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN); ++ this.setTiltAndScheduleTick(state, world, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit + } else if (tilt == Tilt.PARTIAL) { +- this.setTiltAndScheduleTick(state, world, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN); ++ this.setTiltAndScheduleTick(state, world, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit + } else if (tilt == Tilt.FULL) { + BigDripleafBlock.resetTilt(state, world, pos); + } +@@ -220,36 +238,46 @@ + return entity.onGround() && entity.position().y > (double) ((float) pos.getY() + 0.6875F); + } + +- private void setTiltAndScheduleTick(BlockState state, Level world, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound) { +- BigDripleafBlock.setTilt(state, world, pos, tilt); +- if (sound != null) { +- BigDripleafBlock.playTiltSound(world, pos, sound); ++ // CraftBukkit start ++ private void setTiltAndScheduleTick(BlockState iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable SoundEvent soundeffect, @Nullable Entity entity) { ++ if (!BigDripleafBlock.setTilt(iblockdata, world, blockposition, tilt, entity)) return; ++ // CraftBukkit end ++ if (soundeffect != null) { ++ BigDripleafBlock.playTiltSound(world, blockposition, soundeffect); + } + + int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); + + if (i != -1) { +- world.scheduleTick(pos, (Block) this, i); ++ world.scheduleTick(blockposition, (Block) this, i); + } + + } + + private static void resetTilt(BlockState state, Level world, BlockPos pos) { +- BigDripleafBlock.setTilt(state, world, pos, Tilt.NONE); ++ BigDripleafBlock.setTilt(state, world, pos, Tilt.NONE, null); // CraftBukkit + if (state.getValue(BigDripleafBlock.TILT) != Tilt.NONE) { + BigDripleafBlock.playTiltSound(world, pos, SoundEvents.BIG_DRIPLEAF_TILT_UP); + } + + } + +- private static void setTilt(BlockState state, Level world, BlockPos pos, Tilt tilt) { +- Tilt tilt1 = (Tilt) state.getValue(BigDripleafBlock.TILT); ++ // CraftBukkit start ++ private static boolean setTilt(BlockState iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable Entity entity) { ++ if (entity != null) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(BigDripleafBlock.TILT, tilt))) { ++ return false; ++ } ++ } ++ // CraftBukkit end ++ Tilt tilt1 = (Tilt) iblockdata.getValue(BigDripleafBlock.TILT); + +- world.setBlock(pos, (BlockState) state.setValue(BigDripleafBlock.TILT, tilt), 2); ++ world.setBlock(blockposition, (BlockState) iblockdata.setValue(BigDripleafBlock.TILT, tilt), 2); + if (tilt.causesVibration() && tilt != tilt1) { +- world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); ++ world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, blockposition); + } + ++ return true; // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch new file mode 100644 index 0000000000..3317c55a37 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/BlastFurnaceBlock.java ++++ b/net/minecraft/world/level/block/BlastFurnaceBlock.java +@@ -45,8 +45,7 @@ + @Override + protected void openContainer(Level world, BlockPos pos, Player player) { + BlockEntity blockEntity = world.getBlockEntity(pos); +- if (blockEntity instanceof BlastFurnaceBlockEntity) { +- player.openMenu((MenuProvider)blockEntity); ++ if (blockEntity instanceof BlastFurnaceBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_BLAST_FURNACE); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch new file mode 100644 index 0000000000..c1060afc42 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch @@ -0,0 +1,154 @@ +--- a/net/minecraft/world/level/block/Block.java ++++ b/net/minecraft/world/level/block/Block.java +@@ -88,6 +88,21 @@ + public static final int UPDATE_LIMIT = 512; + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || ++ this != Blocks.BARRIER && ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + @Nullable + private Item item; + private static final int CACHE_SIZE = 256; +@@ -295,12 +310,38 @@ + + } + ++ // Paper start - Add BlockBreakBlockEvent ++ public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { ++ if (levelAccessor instanceof ServerLevel serverLevel) { ++ List items = new java.util.ArrayList<>(); ++ for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) { ++ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); ++ } ++ Block block = state.getBlock(); // Paper - Properly handle xp dropping ++ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items); ++ event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping ++ event.callEvent(); ++ for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { ++ popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); ++ } ++ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping ++ block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping ++ } ++ return true; ++ } ++ // Paper end - Add BlockBreakBlockEvent ++ + public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { ++ // Paper start - Properly handle xp dropping ++ dropResources(state, world, pos, blockEntity, entity, tool, true); ++ } ++ public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) { ++ // Paper end - Properly handle xp dropping + if (world instanceof ServerLevel) { + Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { + Block.popResource(world, pos, itemstack1); + }); +- state.spawnAfterBreak((ServerLevel) world, pos, tool, true); ++ state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping + } + + } +@@ -340,7 +381,13 @@ + ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get(); + + entityitem.setDefaultPickUpDelay(); +- world.addFreshEntity(entityitem); ++ // CraftBukkit start ++ if (world.captureDrops != null) { ++ world.captureDrops.add(entityitem); ++ } else { ++ world.addFreshEntity(entityitem); ++ } ++ // CraftBukkit end + return; + } + } +@@ -348,8 +395,13 @@ + } + + public void popExperience(ServerLevel world, BlockPos pos, int size) { ++ // Paper start - add entity parameter ++ popExperience(world, pos, size, null); ++ } ++ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) { ++ // Paper end - add entity parameter + if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { +- ExperienceOrb.award(world, Vec3.atCenterOf(pos), size); ++ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper + } + + } +@@ -367,10 +419,18 @@ + return this.defaultBlockState(); + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix drops not preventing stats/food exhaustion + public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { ++ // Paper start - fix drops not preventing stats/food exhaustion ++ this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true); ++ } ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { ++ // Paper end - fix drops not preventing stats/food exhaustion + player.awardStat(Stats.BLOCK_MINED.get(this)); +- player.causeFoodExhaustion(0.005F); +- Block.dropResources(state, world, pos, blockEntity, player, tool); ++ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent ++ if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion ++ Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping ++ } // Paper - fix drops not preventing stats/food exhaustion + } + + public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} +@@ -490,15 +550,35 @@ + return this.builtInRegistryHolder; + } + +- protected void tryDropExperience(ServerLevel world, BlockPos pos, ItemStack tool, IntProvider experience) { +- int i = EnchantmentHelper.processBlockExperience(world, tool, experience.sample(world.getRandom())); ++ // CraftBukkit start ++ protected int tryDropExperience(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, IntProvider intprovider) { ++ int i = EnchantmentHelper.processBlockExperience(worldserver, itemstack, intprovider.sample(worldserver.getRandom())); + + if (i > 0) { +- this.popExperience(world, pos, i); ++ // this.popExperience(worldserver, blockposition, i); ++ return i; + } + ++ return 0; + } + ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ return 0; ++ } ++ // CraftBukkit end ++ ++ // 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 ++ + private static record ShapePairKey(VoxelShape first, VoxelShape second) { + + public boolean equals(Object object) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch new file mode 100644 index 0000000000..ad19f46261 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/BrewingStandBlock.java ++++ b/net/minecraft/world/level/block/BrewingStandBlock.java +@@ -68,8 +68,7 @@ + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { +- if (!world.isClientSide && world.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity) { +- player.openMenu(brewingStandBlockEntity); ++ if (!world.isClientSide && world.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity && player.openMenu(brewingStandBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_BREWINGSTAND); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch new file mode 100644 index 0000000000..aa97c1238d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -48,6 +48,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockState blockState = world.getBlockState(pos.above()); + if (blockState.isAir()) { + entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch new file mode 100644 index 0000000000..ee6f708bf9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/level/block/BuddingAmethystBlock.java ++++ b/net/minecraft/world/level/block/BuddingAmethystBlock.java +@@ -45,7 +45,13 @@ + if (block != null) { + BlockState iblockdata2 = (BlockState) ((BlockState) block.defaultBlockState().setValue(AmethystClusterBlock.FACING, enumdirection)).setValue(AmethystClusterBlock.WATERLOGGED, iblockdata1.getFluidState().getType() == Fluids.WATER); + +- world.setBlockAndUpdate(blockposition1, iblockdata2); ++ // Paper start - Have Amethyst throw both spread and grow events ++ if (block == Blocks.SMALL_AMETHYST_BUD) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, iblockdata2); // CraftBukkit ++ } else { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, blockposition1, iblockdata2); ++ } ++ // Paper end - Have Amethyst throw both spread and grow events + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch new file mode 100644 index 0000000000..d8fb08b681 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/level/block/BushBlock.java ++++ b/net/minecraft/world/level/block/BushBlock.java +@@ -6,6 +6,7 @@ + import net.minecraft.tags.BlockTags; + import net.minecraft.util.RandomSource; + import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.ScheduledTickAccess; + import net.minecraft.world.level.block.state.BlockBehaviour; +@@ -27,7 +28,15 @@ + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { +- return !state.canSurvive(world, pos) ? Blocks.AIR.defaultBlockState() : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); ++ // CraftBukkit start ++ if (!state.canSurvive(world, pos)) { ++ // Suppress during worldgen ++ if (!(world instanceof net.minecraft.server.level.ServerLevel world1 && world1.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world1, pos).isCancelled()) { // Paper ++ return Blocks.AIR.defaultBlockState(); ++ } ++ } ++ return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch new file mode 100644 index 0000000000..8311b29036 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch @@ -0,0 +1,78 @@ +--- a/net/minecraft/world/level/block/ButtonBlock.java ++++ b/net/minecraft/world/level/block/ButtonBlock.java +@@ -34,6 +34,10 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.event.block.BlockRedstoneEvent; ++import org.bukkit.event.entity.EntityInteractEvent; ++// CraftBukkit end + + public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { + +@@ -126,6 +130,19 @@ + if ((Boolean) state.getValue(ButtonBlock.POWERED)) { + return InteractionResult.CONSUME; + } else { ++ // CraftBukkit start ++ boolean powered = ((Boolean) state.getValue(ButtonBlock.POWERED)); ++ org.bukkit.block.Block block = world.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); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end + this.press(state, world, pos, player); + return InteractionResult.SUCCESS; + } +@@ -191,17 +208,43 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) { + this.checkPressed(state, world, pos); + } + } + + protected void checkPressed(BlockState state, Level world, BlockPos pos) { +- AbstractArrow entityarrow = this.type.canButtonBeActivatedByArrows() ? (AbstractArrow) world.getEntitiesOfClass(AbstractArrow.class, state.getShape(world, pos).bounds().move(pos)).stream().findFirst().orElse((Object) null) : null; ++ AbstractArrow entityarrow = this.type.canButtonBeActivatedByArrows() ? (AbstractArrow) world.getEntitiesOfClass(AbstractArrow.class, state.getShape(world, pos).bounds().move(pos)).stream().findFirst().orElse(null) : null; // CraftBukkit - decompile error + boolean flag = entityarrow != null; + boolean flag1 = (Boolean) state.getValue(ButtonBlock.POWERED); + ++ // CraftBukkit start - Call interact event when arrows turn on wooden buttons ++ if (flag1 != flag && flag) { ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ EntityInteractEvent event = new EntityInteractEvent(entityarrow.getBukkitEntity(), block); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end ++ + if (flag != flag1) { ++ // CraftBukkit start ++ boolean powered = flag1; ++ org.bukkit.block.Block block = world.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); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if ((flag && eventRedstone.getNewCurrent() <= 0) || (!flag && eventRedstone.getNewCurrent() > 0)) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(ButtonBlock.POWERED, flag), 3); + this.updateNeighbours(state, world, pos); + this.playSound((Player) null, world, pos, flag); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch new file mode 100644 index 0000000000..c7084f8b41 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/level/block/CactusBlock.java ++++ b/net/minecraft/world/level/block/CactusBlock.java +@@ -22,6 +22,7 @@ + import net.minecraft.world.level.redstone.Orientation; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class CactusBlock extends Block { + +@@ -61,16 +62,17 @@ + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height + int j = (Integer) state.getValue(CactusBlock.AGE); + +- if (j == 15) { +- world.setBlockAndUpdate(blockposition1, this.defaultBlockState()); ++ int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution ++ if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution ++ CraftEventFactory.handleBlockGrowEvent(world, blockposition1, this.defaultBlockState()); // CraftBukkit + BlockState iblockdata1 = (BlockState) state.setValue(CactusBlock.AGE, 0); + + world.setBlock(pos, iblockdata1, 4); + world.neighborChanged(iblockdata1, blockposition1, this, (Orientation) null, false); +- } else { ++ } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution + world.setBlock(pos, (BlockState) state.setValue(CactusBlock.AGE, j + 1), 4); + } + +@@ -120,7 +122,8 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { +- entity.hurt(world.damageSources().cactus(), 1.0F); ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent ++ entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch new file mode 100644 index 0000000000..346de8857c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch @@ -0,0 +1,47 @@ +--- a/net/minecraft/world/level/block/CakeBlock.java ++++ b/net/minecraft/world/level/block/CakeBlock.java +@@ -66,6 +66,12 @@ + if (block instanceof CandleBlock) { + CandleBlock candleblock = (CandleBlock) block; + ++ // Paper start - call change block event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(candleblock))) { ++ player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease ++ return InteractionResult.TRY_WITH_EMPTY_HAND; ++ } ++ // Paper end - call change block event + stack.consume(1, player); + world.playSound((Player) null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlockAndUpdate(pos, CandleCakeBlock.byCandle(candleblock)); +@@ -97,10 +103,29 @@ + if (!player.canEat(false)) { + return InteractionResult.PASS; + } else { ++ // Paper start - call change block event ++ int i = state.getValue(CakeBlock.BITES); ++ final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock(); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) { ++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); ++ return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles ++ } ++ // Paper end - call change block event + player.awardStat(Stats.EAT_CAKE_SLICE); +- player.getFoodData().eat(2, 0.1F); +- int i = (Integer) state.getValue(CakeBlock.BITES); ++ // CraftBukkit start ++ // entityhuman.getFoodData().eat(2, 0.1F); ++ int oldFoodLevel = player.getFoodData().foodLevel; + ++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel); ++ ++ if (!event.isCancelled()) { ++ player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F); ++ } ++ ++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); ++ // CraftBukkit end ++ // Paper - move up ++ + world.gameEvent((Entity) player, (Holder) GameEvent.EAT, pos); + if (i < 6) { + world.setBlock(pos, (BlockState) state.setValue(CakeBlock.BITES, i + 1), 3); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch new file mode 100644 index 0000000000..133764e05b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/CampfireBlock.java ++++ b/net/minecraft/world/level/block/CampfireBlock.java +@@ -112,8 +112,9 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity) { +- entity.hurt(world.damageSources().campfire(), (float) this.fireDamage); ++ entity.hurt(world.damageSources().campfire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit + } + + super.entityInside(state, world, pos, entity); +@@ -219,6 +220,11 @@ + + if (world instanceof ServerLevel worldserver) { + if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition) && !(Boolean) state.getValue(CampfireBlock.LIT) && !(Boolean) state.getValue(CampfireBlock.WATERLOGGED)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, projectile).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(blockposition, (BlockState) state.setValue(BlockStateProperties.LIT, true), 11); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch new file mode 100644 index 0000000000..237e4da1be --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/CartographyTableBlock.java ++++ b/net/minecraft/world/level/block/CartographyTableBlock.java +@@ -32,8 +32,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_CARTOGRAPHY_TABLE); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch new file mode 100644 index 0000000000..8b9fd76990 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java ++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java +@@ -24,6 +24,9 @@ + import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder; + import net.minecraft.world.level.block.state.predicate.BlockStatePredicate; + import net.minecraft.world.level.block.state.properties.EnumProperty; ++// CraftBukkit start ++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; ++// CraftBukkit end + + public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + +@@ -87,9 +90,14 @@ + } + + private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) { +- CarvedPumpkinBlock.clearPatternBlocks(world, patternResult); ++ // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down + entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F); +- world.addFreshEntity(entity); ++ // CraftBukkit start ++ if (!world.addFreshEntity(entity, (entity.getType() == EntityType.SNOW_GOLEM) ? SpawnReason.BUILD_SNOWMAN : SpawnReason.BUILD_IRONGOLEM)) { ++ return; ++ } ++ CarvedPumpkinBlock.clearPatternBlocks(world, patternResult); // CraftBukkit - from above ++ // CraftBukkit end + Iterator iterator = world.getEntitiesOfClass(ServerPlayer.class, entity.getBoundingBox().inflate(5.0D)).iterator(); + + while (iterator.hasNext()) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch new file mode 100644 index 0000000000..0d9def53a1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch @@ -0,0 +1,56 @@ +--- a/net/minecraft/world/level/block/CauldronBlock.java ++++ b/net/minecraft/world/level/block/CauldronBlock.java +@@ -12,6 +12,9 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.material.Fluids; ++// CraftBukkit start ++import org.bukkit.event.block.CauldronLevelChangeEvent; ++// CraftBukkit end + + public class CauldronBlock extends AbstractCauldronBlock { + +@@ -41,9 +44,19 @@ + public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { + if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) { + if (precipitation == Biome.Precipitation.RAIN) { ++ // Paper start - Call CauldronLevelChangeEvent ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); + world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); + } else if (precipitation == Biome.Precipitation.SNOW) { ++ // Paper start - Call CauldronLevelChangeEvent ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()); + world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); + } +@@ -62,13 +75,19 @@ + + if (fluid == Fluids.WATER) { + iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState(); +- world.setBlockAndUpdate(pos, iblockdata1); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); ++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.levelEvent(1047, pos, 0); + } else if (fluid == Fluids.LAVA) { + iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState(); +- world.setBlockAndUpdate(pos, iblockdata1); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); ++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.levelEvent(1046, pos, 0); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch new file mode 100644 index 0000000000..b340d9c9cb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/level/block/CaveVines.java ++++ b/net/minecraft/world/level/block/CaveVines.java +@@ -19,6 +19,13 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.shapes.VoxelShape; + ++// CraftBukkit start ++import java.util.Collections; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.player.PlayerHarvestBlockEvent; ++// CraftBukkit end ++ + public interface CaveVines { + + VoxelShape SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D); +@@ -26,7 +33,24 @@ + + static InteractionResult use(@Nullable Entity picker, BlockState state, Level world, BlockPos pos) { + if ((Boolean) state.getValue(CaveVines.BERRIES)) { +- Block.popResource(world, pos, new ItemStack(Items.GLOW_BERRIES, 1)); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(picker, pos, (BlockState) state.setValue(CaveVines.BERRIES, false))) { ++ return InteractionResult.SUCCESS; ++ } ++ ++ if (picker instanceof Player) { ++ PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(world, pos, (Player) picker, net.minecraft.world.InteractionHand.MAIN_HAND, Collections.singletonList(new ItemStack(Items.GLOW_BERRIES, 1))); ++ if (event.isCancelled()) { ++ return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block ++ } ++ for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) { ++ Block.popResource(world, pos, CraftItemStack.asNMSCopy(itemStack)); ++ } ++ } else { ++ Block.popResource(world, pos, new ItemStack(Items.GLOW_BERRIES, 1)); ++ } ++ // CraftBukkit end ++ + float f = Mth.randomBetween(world.random, 0.8F, 1.2F); + + world.playSound((Player) null, pos, SoundEvents.CAVE_VINES_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, f); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch new file mode 100644 index 0000000000..511200af88 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/level/block/CaveVinesBlock.java ++++ b/net/minecraft/world/level/block/CaveVinesBlock.java +@@ -50,9 +50,18 @@ + return to.setValue(BERRIES, from.getValue(BERRIES)); + } + ++ // Paper start - Fix Spigot growth modifiers + @Override ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F); ++ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value); ++ } ++ // Paper end - Fix Spigot growth modifiers ++ ++ @Override + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { +- return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F)); ++ // Paper start - Fix Spigot growth modifiers ++ return this.getGrowIntoState(state, random, null); + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch new file mode 100644 index 0000000000..b85ba5b3dc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/CeilingHangingSignBlock.java ++++ b/net/minecraft/world/level/block/CeilingHangingSignBlock.java +@@ -159,6 +159,6 @@ + @Nullable + @Override + public BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType type) { +- return createTickerHelper(type, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick); ++ return null; // Craftbukkit - remove unnecessary sign ticking + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch new file mode 100644 index 0000000000..94b24017e5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java ++++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java +@@ -20,7 +20,7 @@ + + if (random.nextFloat() < 0.05688889F) { + this.getNextState(state, world, pos, random).ifPresent((iblockdata1) -> { +- world.setBlockAndUpdate(pos, iblockdata1); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, iblockdata1); // CraftBukkit + }); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch new file mode 100644 index 0000000000..4b025cb2e4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch @@ -0,0 +1,118 @@ +--- a/net/minecraft/world/level/block/ChestBlock.java ++++ b/net/minecraft/world/level/block/ChestBlock.java +@@ -91,24 +91,7 @@ + public Optional acceptDouble(final ChestBlockEntity first, final ChestBlockEntity second) { + final CompoundContainer inventorylargechest = new CompoundContainer(first, second); + +- return Optional.of(new MenuProvider(this) { +- @Nullable +- @Override +- public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- if (first.canOpen(player) && second.canOpen(player)) { +- first.unpackLootTable(playerInventory.player); +- second.unpackLootTable(playerInventory.player); +- return ChestMenu.sixRows(syncId, playerInventory, inventorylargechest); +- } else { +- return null; +- } +- } +- +- @Override +- public Component getDisplayName() { +- return (Component) (first.hasCustomName() ? first.getDisplayName() : (second.hasCustomName() ? second.getDisplayName() : Component.translatable("container.chestDouble"))); +- } +- }); ++ return Optional.of(new DoubleInventory(first, second, inventorylargechest)); // CraftBukkit // CraftBukkit - decompile error + } + + public Optional acceptSingle(ChestBlockEntity single) { +@@ -118,8 +101,40 @@ + @Override + public Optional acceptNone() { + return Optional.empty(); ++ } ++ }; ++ ++ // CraftBukkit start ++ public static class DoubleInventory implements MenuProvider { ++ ++ private final ChestBlockEntity tileentitychest; ++ private final ChestBlockEntity tileentitychest1; ++ public final CompoundContainer inventorylargechest; ++ ++ public DoubleInventory(ChestBlockEntity tileentitychest, ChestBlockEntity tileentitychest1, CompoundContainer inventorylargechest) { ++ this.tileentitychest = tileentitychest; ++ this.tileentitychest1 = tileentitychest1; ++ this.inventorylargechest = inventorylargechest; ++ } ++ ++ @Nullable ++ @Override ++ public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { ++ if (this.tileentitychest.canOpen(player) && this.tileentitychest1.canOpen(player)) { ++ this.tileentitychest.unpackLootTable(playerInventory.player); ++ this.tileentitychest1.unpackLootTable(playerInventory.player); ++ return ChestMenu.sixRows(syncId, playerInventory, this.inventorylargechest); ++ } else { ++ return null; ++ } + } ++ ++ @Override ++ public Component getDisplayName() { ++ return (Component) (this.tileentitychest.hasCustomName() ? this.tileentitychest.getDisplayName() : (this.tileentitychest1.hasCustomName() ? this.tileentitychest1.getDisplayName() : Component.translatable("container.chestDouble"))); ++ } + }; ++ // CraftBukkit end + + @Override + public MapCodec codec() { +@@ -232,8 +247,7 @@ + if (world instanceof ServerLevel worldserver) { + MenuProvider itileinventory = this.getMenuProvider(state, world, pos); + +- if (itileinventory != null) { +- player.openMenu(itileinventory); ++ if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(this.getOpenChestStat()); + PiglinAi.angerNearbyPiglins(worldserver, player, true); + } +@@ -257,7 +271,7 @@ + + @Override + public DoubleBlockCombiner.NeighborCombineResult combine(BlockState state, Level world, BlockPos pos, boolean ignoreBlocked) { +- BiPredicate bipredicate; ++ BiPredicate bipredicate; // CraftBukkit - decompile error + + if (ignoreBlocked) { + bipredicate = (generatoraccess, blockposition1) -> { +@@ -273,9 +287,16 @@ + @Nullable + @Override + public MenuProvider getMenuProvider(BlockState state, Level world, BlockPos pos) { +- return (MenuProvider) ((Optional) this.combine(state, world, pos, false).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null); ++ // CraftBukkit start ++ return this.getMenuProvider(state, world, pos, false); + } + ++ @Nullable ++ public MenuProvider getMenuProvider(BlockState iblockdata, Level world, BlockPos blockposition, boolean ignoreObstructions) { ++ return (MenuProvider) ((Optional) this.combine(iblockdata, world, blockposition, ignoreObstructions).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null); ++ // CraftBukkit end ++ } ++ + public static DoubleBlockCombiner.Combiner opennessCombiner(final LidBlockEntity progress) { + return new DoubleBlockCombiner.Combiner() { + public Float2FloatFunction acceptDouble(ChestBlockEntity first, ChestBlockEntity second) { +@@ -321,6 +342,11 @@ + } + + private static boolean isCatSittingOnChest(LevelAccessor world, BlockPos pos) { ++ // Paper start - Option to disable chest cat detection ++ if (world.getMinecraftWorld().paperConfig().entities.behavior.disableChestCatDetection) { ++ return false; ++ } ++ // Paper end - Option to disable chest cat detection + List list = world.getEntitiesOfClass(Cat.class, new AABB((double) pos.getX(), (double) (pos.getY() + 1), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 2), (double) (pos.getZ() + 1))); + + if (!list.isEmpty()) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch new file mode 100644 index 0000000000..88160b2d20 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch @@ -0,0 +1,73 @@ +--- a/net/minecraft/world/level/block/ChorusFlowerBlock.java ++++ b/net/minecraft/world/level/block/ChorusFlowerBlock.java +@@ -23,6 +23,8 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.VoxelShape; + ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++ + public class ChorusFlowerBlock extends Block { + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { +@@ -103,8 +105,12 @@ + } + + if (flag && ChorusFlowerBlock.allNeighborsEmpty(world, blockposition1, (Direction) null) && world.isEmptyBlock(pos.above(2))) { +- world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2); +- this.placeGrownFlower(world, blockposition1, i); ++ // CraftBukkit start - add event ++ if (CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i)), 2)) { ++ world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2); ++ this.placeGrownFlower(world, blockposition1, i); ++ } ++ // CraftBukkit end + } else if (i < 4) { + j = random.nextInt(4); + if (flag1) { +@@ -118,18 +124,30 @@ + BlockPos blockposition2 = pos.relative(enumdirection); + + if (world.isEmptyBlock(blockposition2) && world.isEmptyBlock(blockposition2.below()) && ChorusFlowerBlock.allNeighborsEmpty(world, blockposition2, enumdirection.getOpposite())) { +- this.placeGrownFlower(world, blockposition2, i + 1); +- flag2 = true; ++ // CraftBukkit start - add event ++ if (CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i + 1)), 2)) { ++ this.placeGrownFlower(world, blockposition2, i + 1); ++ flag2 = true; ++ } ++ // CraftBukkit end + } + } + + if (flag2) { + world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2); + } else { +- this.placeDeadFlower(world, pos); ++ // CraftBukkit start - add event ++ if (CraftEventFactory.handleBlockGrowEvent(world, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) { ++ this.placeDeadFlower(world, pos); ++ } ++ // CraftBukkit end + } + } else { +- this.placeDeadFlower(world, pos); ++ // CraftBukkit start - add event ++ if (CraftEventFactory.handleBlockGrowEvent(world, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) { ++ this.placeDeadFlower(world, pos); ++ } ++ // CraftBukkit end + } + + } +@@ -267,6 +285,11 @@ + + if (world instanceof ServerLevel worldserver) { + if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) { ++ // CraftBukkit ++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ return; ++ } ++ // CraftBukkit end + world.destroyBlock(blockposition, true, projectile); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch new file mode 100644 index 0000000000..80c24699ea --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/ChorusPlantBlock.java ++++ b/net/minecraft/world/level/block/ChorusPlantBlock.java +@@ -38,6 +38,7 @@ + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates + return getStateWithConnections(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); + } + +@@ -68,6 +69,7 @@ + BlockState neighborState, + RandomSource random + ) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return state; // Paper - add option to disable block updates + if (!state.canSurvive(world, pos)) { + tickView.scheduleTick(pos, this, 1); + return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -79,6 +81,7 @@ + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return; // Paper - add option to disable block updates + if (!state.canSurvive(world, pos)) { + world.destroyBlock(pos, true); + } +@@ -86,6 +89,7 @@ + + @Override + protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return true; // Paper - add option to disable block updates + BlockState blockState = world.getBlockState(pos.below()); + boolean bl = !world.getBlockState(pos.above()).isAir() && !blockState.isAir(); + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch new file mode 100644 index 0000000000..963cb2ef7b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/level/block/CocoaBlock.java ++++ b/net/minecraft/world/level/block/CocoaBlock.java +@@ -20,6 +20,7 @@ + import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class CocoaBlock extends HorizontalDirectionalBlock implements BonemealableBlock { + +@@ -57,11 +58,11 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if (world.random.nextInt(5) == 0) { ++ if (world.random.nextFloat() < (world.spigotConfig.cocoaModifier / (100.0f * 5))) { // Spigot - SPIGOT-7159: Better modifier resolution + int i = (Integer) state.getValue(CocoaBlock.AGE); + + if (i < 2) { +- world.setBlock(pos, (BlockState) state.setValue(CocoaBlock.AGE, i + 1), 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(CocoaBlock.AGE, i + 1), 2); // CraftBukkkit + } + } + +@@ -131,7 +132,7 @@ + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { +- world.setBlock(pos, (BlockState) state.setValue(CocoaBlock.AGE, (Integer) state.getValue(CocoaBlock.AGE) + 1), 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(CocoaBlock.AGE, (Integer) state.getValue(CocoaBlock.AGE) + 1), 2); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch new file mode 100644 index 0000000000..6c6c7fbbd8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/block/CommandBlock.java ++++ b/net/minecraft/world/level/block/CommandBlock.java +@@ -31,6 +31,8 @@ + import net.minecraft.world.phys.BlockHitResult; + import org.slf4j.Logger; + ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++ + public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> { +@@ -78,7 +80,16 @@ + + private void setPoweredAndUpdate(Level world, BlockPos pos, CommandBlockEntity blockEntity, boolean powered) { + boolean flag1 = blockEntity.isPowered(); ++ // CraftBukkit start ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int old = flag1 ? 15 : 0; ++ int current = powered ? 15 : 0; + ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, old, current); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ powered = eventRedstone.getNewCurrent() > 0; ++ // CraftBukkit end ++ + if (powered != flag1) { + blockEntity.setPowered(powered); + if (powered) { +@@ -141,7 +152,7 @@ + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { ++ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + player.openCommandBlock((CommandBlockEntity) tileentity); + return InteractionResult.SUCCESS; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch new file mode 100644 index 0000000000..a59662f5c7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/block/ComparatorBlock.java ++++ b/net/minecraft/world/level/block/ComparatorBlock.java +@@ -27,6 +27,7 @@ + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.ticks.TickPriority; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class ComparatorBlock extends DiodeBlock implements EntityBlock { + +@@ -110,7 +111,8 @@ + + @Nullable + private ItemFrame getItemFrame(Level world, Direction facing, BlockPos pos) { +- List list = world.getEntitiesOfClass(ItemFrame.class, new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1)), (entityitemframe) -> { ++ // CraftBukkit - decompile error ++ List list = world.getEntitiesOfClass(ItemFrame.class, new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1)), (java.util.function.Predicate) (entityitemframe) -> { + return entityitemframe != null && entityitemframe.getDirection() == facing; + }); + +@@ -163,8 +165,18 @@ + boolean flag1 = (Boolean) state.getValue(ComparatorBlock.POWERED); + + if (flag1 && !flag) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(ComparatorBlock.POWERED, false), 2); + } else if (!flag1 && flag) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(ComparatorBlock.POWERED, true), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch new file mode 100644 index 0000000000..919fb7f0cb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch @@ -0,0 +1,184 @@ +--- a/net/minecraft/world/level/block/ComposterBlock.java ++++ b/net/minecraft/world/level/block/ComposterBlock.java +@@ -41,6 +41,10 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder; ++import org.bukkit.craftbukkit.util.DummyGeneratorAccess; ++// CraftBukkit end + + public class ComposterBlock extends Block implements WorldlyContainerHolder { + +@@ -241,6 +245,11 @@ + if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { + if (i < 7 && !world.isClientSide) { + BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(stack.getItem())); +@@ -269,7 +278,19 @@ + int i = (Integer) state.getValue(ComposterBlock.LEVEL); + + if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { +- BlockState iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack); ++ // CraftBukkit start ++ double rand = world.getRandom().nextDouble(); ++ BlockState iblockdata1 = null; // Paper ++ if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem ++ return state; ++ } ++ iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return state; ++ } ++ // Paper end ++ // CraftBukkit end + + stack.shrink(1); + return iblockdata1; +@@ -279,6 +300,14 @@ + } + + public static BlockState extractProduce(Entity user, BlockState state, Level world, BlockPos pos) { ++ // CraftBukkit start ++ if (user != null && !(user instanceof Player)) { ++ BlockState iblockdata1 = ComposterBlock.empty(user, state, DummyGeneratorAccess.INSTANCE, pos); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) { ++ return state; ++ } ++ } ++ // CraftBukkit end + if (!world.isClientSide) { + Vec3 vec3d = Vec3.atLowerCornerWithOffset(pos, 0.5D, 1.01D, 0.5D).offsetRandom(world.random, 0.7F); + ItemEntity entityitem = new ItemEntity(world, vec3d.x(), vec3d.y(), vec3d.z(), new ItemStack(Items.BONE_MEAL)); +@@ -301,20 +330,47 @@ + return iblockdata1; + } + ++ @Nullable // Paper + static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) { +- int i = (Integer) state.getValue(ComposterBlock.LEVEL); +- float f = ComposterBlock.COMPOSTABLES.getFloat(stack.getItem()); ++ // CraftBukkit start ++ return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble()); ++ } + +- if ((i != 0 || f <= 0.0F) && world.getRandom().nextDouble() >= (double) f) { +- return state; +- } else { +- int j = i + 1; +- BlockState iblockdata1 = (BlockState) state.setValue(ComposterBlock.LEVEL, j); ++ @Nullable // Paper - make it nullable ++ static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) { ++ // CraftBukkit end ++ int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); ++ float f = ComposterBlock.COMPOSTABLES.getFloat(itemstack.getItem()); + +- world.setBlock(pos, iblockdata1, 3); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(user, iblockdata1)); ++ // Paper start - Add CompostItemEvent and EntityCompostItemEvent ++ boolean willRaiseLevel = !((i != 0 || f <= 0.0F) && rand >= (double) f); ++ final io.papermc.paper.event.block.CompostItemEvent event; ++ if (entity == null) { ++ event = new io.papermc.paper.event.block.CompostItemEvent(org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); ++ } else { ++ event = new io.papermc.paper.event.entity.EntityCompostItemEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); ++ } ++ if (!event.callEvent()) { // check for cancellation of entity event (non entity event can't be cancelled cause of hoppers) ++ return null; ++ } ++ willRaiseLevel = event.willRaiseLevel(); ++ ++ if (!willRaiseLevel) { ++ // Paper end - Add CompostItemEvent and EntityCompostItemEvent ++ return iblockdata; ++ } else { ++ int j = i + 1; ++ BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j); ++ // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events ++ if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) { ++ return null; ++ } ++ // Paper end ++ ++ generatoraccess.setBlock(blockposition, iblockdata1, 3); ++ generatoraccess.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1)); + if (j == 7) { +- world.scheduleTick(pos, state.getBlock(), 20); ++ generatoraccess.scheduleTick(blockposition, iblockdata.getBlock(), 20); + } + + return iblockdata1; +@@ -354,7 +410,8 @@ + public WorldlyContainer getContainer(BlockState state, LevelAccessor world, BlockPos pos) { + int i = (Integer) state.getValue(ComposterBlock.LEVEL); + +- return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(state, world, pos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(state, world, pos) : new ComposterBlock.EmptyContainer())); ++ // CraftBukkit - empty generatoraccess, blockposition ++ return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(state, world, pos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(state, world, pos) : new ComposterBlock.EmptyContainer(world, pos))); + } + + public static class OutputContainer extends SimpleContainer implements WorldlyContainer { +@@ -369,6 +426,7 @@ + this.state = state; + this.level = world; + this.pos = pos; ++ this.bukkitOwner = new CraftBlockInventoryHolder(world, pos, this); // CraftBukkit + } + + @Override +@@ -393,8 +451,15 @@ + + @Override + public void setChanged() { ++ // CraftBukkit start - allow putting items back (eg cancelled InventoryMoveItemEvent) ++ if (this.isEmpty()) { + ComposterBlock.empty((Entity) null, this.state, this.level, this.pos); + this.changed = true; ++ } else { ++ this.level.setBlock(this.pos, this.state, 3); ++ this.changed = false; ++ } ++ // CraftBukkit end + } + } + +@@ -407,6 +472,7 @@ + + public InputContainer(BlockState state, LevelAccessor world, BlockPos pos) { + super(1); ++ this.bukkitOwner = new CraftBlockInventoryHolder(world, pos, this); // CraftBukkit + this.state = state; + this.level = world; + this.pos = pos; +@@ -439,6 +505,11 @@ + if (!itemstack.isEmpty()) { + this.changed = true; + BlockState iblockdata = ComposterBlock.addItem((Entity) null, this.state, this.level, this.pos, itemstack); ++ // Paper start - Add CompostItemEvent and EntityCompostItemEvent ++ if (iblockdata == null) { ++ return; ++ } ++ // Paper end - Add CompostItemEvent and EntityCompostItemEvent + + this.level.levelEvent(1500, this.pos, iblockdata != this.state ? 1 : 0); + this.removeItemNoUpdate(0); +@@ -449,8 +520,9 @@ + + public static class EmptyContainer extends SimpleContainer implements WorldlyContainer { + +- public EmptyContainer() { ++ public EmptyContainer(LevelAccessor generatoraccess, BlockPos blockposition) { // CraftBukkit + super(0); ++ this.bukkitOwner = new CraftBlockInventoryHolder(generatoraccess, blockposition, this); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch new file mode 100644 index 0000000000..5778abe978 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/level/block/ConcretePowderBlock.java ++++ b/net/minecraft/world/level/block/ConcretePowderBlock.java +@@ -15,6 +15,11 @@ + import net.minecraft.world.level.ScheduledTickAccess; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockStates; ++import org.bukkit.event.block.BlockFormEvent; ++// CraftBukkit end + + public class ConcretePowderBlock extends FallingBlock { + +@@ -38,7 +43,7 @@ + @Override + public void onLand(Level world, BlockPos pos, BlockState fallingBlockState, BlockState currentStateInPos, FallingBlockEntity fallingBlockEntity) { + if (ConcretePowderBlock.shouldSolidify(world, pos, currentStateInPos)) { +- world.setBlock(pos, this.concrete.defaultBlockState(), 3); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, this.concrete.defaultBlockState(), 3); // CraftBukkit + } + + } +@@ -49,7 +54,24 @@ + BlockPos blockposition = ctx.getClickedPos(); + BlockState iblockdata = world.getBlockState(blockposition); + +- return ConcretePowderBlock.shouldSolidify(world, blockposition, iblockdata) ? this.concrete.defaultBlockState() : super.getStateForPlacement(ctx); ++ // CraftBukkit start ++ if (!ConcretePowderBlock.shouldSolidify(world, blockposition, iblockdata)) { ++ return super.getStateForPlacement(ctx); ++ } ++ ++ // TODO: An event factory call for methods like this ++ CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockposition); ++ blockState.setData(this.concrete.defaultBlockState()); ++ ++ BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState); ++ world.getServer().server.getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ return blockState.getHandle(); ++ } ++ ++ return super.getStateForPlacement(ctx); ++ // CraftBukkit end + } + + private static boolean shouldSolidify(BlockGetter world, BlockPos pos, BlockState state) { +@@ -85,7 +107,25 @@ + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { +- return ConcretePowderBlock.touchesLiquid(world, pos) ? this.concrete.defaultBlockState() : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); ++ // CraftBukkit start ++ if (ConcretePowderBlock.touchesLiquid(world, pos)) { ++ // Suppress during worldgen ++ if (!(world instanceof Level world1)) { ++ return this.concrete.defaultBlockState(); ++ } ++ CraftBlockState blockState = CraftBlockStates.getBlockState(world1, pos); ++ blockState.setData(this.concrete.defaultBlockState()); ++ ++ BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState); ++ world1.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ return blockState.getHandle(); ++ } ++ } ++ ++ return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch new file mode 100644 index 0000000000..389b3b96ec --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/CoralBlock.java ++++ b/net/minecraft/world/level/block/CoralBlock.java +@@ -40,6 +40,11 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!this.scanForWater(world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, this.deadBlock.defaultBlockState(), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch new file mode 100644 index 0000000000..295f4f0049 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/CoralFanBlock.java ++++ b/net/minecraft/world/level/block/CoralFanBlock.java +@@ -41,6 +41,11 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!scanForWater(state, world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false)).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch new file mode 100644 index 0000000000..bccf3cd4a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/CoralPlantBlock.java ++++ b/net/minecraft/world/level/block/CoralPlantBlock.java +@@ -46,6 +46,11 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!scanForWater(state, world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false)).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch new file mode 100644 index 0000000000..172e22d8b2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/CoralWallFanBlock.java ++++ b/net/minecraft/world/level/block/CoralWallFanBlock.java +@@ -41,6 +41,11 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!scanForWater(state, world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false).setValue(CoralWallFanBlock.FACING, state.getValue(CoralWallFanBlock.FACING))).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) ((BlockState) this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false)).setValue(CoralWallFanBlock.FACING, (Direction) state.getValue(CoralWallFanBlock.FACING)), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch new file mode 100644 index 0000000000..b3f1ac312a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch @@ -0,0 +1,89 @@ +--- a/net/minecraft/world/level/block/CrafterBlock.java ++++ b/net/minecraft/world/level/block/CrafterBlock.java +@@ -12,6 +12,7 @@ + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.RandomSource; ++import net.minecraft.world.CompoundContainer; + import net.minecraft.world.Container; + import net.minecraft.world.Containers; + import net.minecraft.world.InteractionResult; +@@ -39,6 +40,12 @@ + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.CrafterCraftEvent; ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.inventory.Inventory; ++// CraftBukkit end + + public class CrafterBlock extends BaseEntityBlock { + +@@ -189,6 +196,13 @@ + RecipeHolder recipeholder = (RecipeHolder) optional.get(); + ItemStack itemstack = ((CraftingRecipe) recipeholder.value()).assemble(craftinginput, world.registryAccess()); + ++ // CraftBukkit start ++ CrafterCraftEvent event = CraftEventFactory.callCrafterCraftEvent(pos, world, crafterblockentity, itemstack, recipeholder); ++ if (event.isCancelled()) { ++ return; ++ } ++ itemstack = CraftItemStack.asNMSCopy(event.getResult()); ++ // CraftBukkit end + if (itemstack.isEmpty()) { + world.levelEvent(1050, pos, 0); + } else { +@@ -227,7 +241,25 @@ + ItemStack itemstack1 = stack.copy(); + + if (iinventory != null && (iinventory instanceof CrafterBlockEntity || stack.getCount() > iinventory.getMaxStackSize(stack))) { ++ // CraftBukkit start - InventoryMoveItemEvent ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack1); ++ ++ Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); + while (!itemstack1.isEmpty()) { ++ if (event.isCancelled()) { ++ break; ++ } ++ // CraftBukkit end + ItemStack itemstack2 = itemstack1.copyWithCount(1); + ItemStack itemstack3 = HopperBlockEntity.addItem(blockEntity, iinventory, itemstack2, enumdirection.getOpposite()); + +@@ -238,7 +270,25 @@ + itemstack1.shrink(1); + } + } else if (iinventory != null) { ++ // CraftBukkit start - InventoryMoveItemEvent ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack1); ++ ++ Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); + while (!itemstack1.isEmpty()) { ++ if (event.isCancelled()) { ++ break; ++ } ++ // CraftBukkit end + int i = itemstack1.getCount(); + + itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, itemstack1, enumdirection.getOpposite()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch new file mode 100644 index 0000000000..e611ea83b5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/CraftingTableBlock.java ++++ b/net/minecraft/world/level/block/CraftingTableBlock.java +@@ -31,8 +31,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_CRAFTING_TABLE); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch new file mode 100644 index 0000000000..4883d93884 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch @@ -0,0 +1,59 @@ +--- a/net/minecraft/world/level/block/CropBlock.java ++++ b/net/minecraft/world/level/block/CropBlock.java +@@ -21,6 +21,7 @@ + import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class CropBlock extends BushBlock implements BonemealableBlock { + +@@ -82,9 +83,26 @@ + if (i < this.getMaxAge()) { + float f = CropBlock.getGrowthSpeed(this, world, pos); + +- if (random.nextInt((int) (25.0F / f) + 1) == 0) { +- world.setBlock(pos, this.getStateForAge(i + 1), 2); ++ // Spigot start ++ int modifier; ++ if (this == Blocks.BEETROOTS) { ++ modifier = world.spigotConfig.beetrootModifier; ++ } else if (this == Blocks.CARROTS) { ++ modifier = world.spigotConfig.carrotModifier; ++ } else if (this == Blocks.POTATOES) { ++ modifier = world.spigotConfig.potatoModifier; ++ // Paper start - Fix Spigot growth modifiers ++ } else if (this == Blocks.TORCHFLOWER_CROP) { ++ modifier = world.spigotConfig.torchFlowerModifier; ++ // Paper end - Fix Spigot growth modifiers ++ } else { ++ modifier = world.spigotConfig.wheatModifier; + } ++ ++ if (random.nextFloat() < (modifier / (100.0f * (Math.floor((25.0F / f) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution ++ // Spigot end ++ CraftEventFactory.handleBlockGrowEvent(world, pos, this.getStateForAge(i + 1), 2); // CraftBukkit ++ } + } + } + +@@ -98,7 +116,7 @@ + i = j; + } + +- world.setBlock(pos, this.getStateForAge(i), 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, this.getStateForAge(i), 2); // CraftBukkit + } + + protected int getBonemealAgeIncrease(Level world) { +@@ -160,8 +178,9 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { +- if (entity instanceof Ravager && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit + worldserver.destroyBlock(pos, true, entity); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch new file mode 100644 index 0000000000..4631339b55 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/DaylightDetectorBlock.java ++++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java +@@ -74,6 +74,7 @@ + + i = Mth.clamp(i, 0, 15); + if ((Integer) state.getValue(DaylightDetectorBlock.POWER) != i) { ++ i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, ((Integer) state.getValue(DaylightDetectorBlock.POWER)), i).getNewCurrent(); // CraftBukkit - Call BlockRedstoneEvent + world.setBlock(pos, (BlockState) state.setValue(DaylightDetectorBlock.POWER, i), 3); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch new file mode 100644 index 0000000000..6b148eba67 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/DecoratedPotBlock.java ++++ b/net/minecraft/world/level/block/DecoratedPotBlock.java +@@ -240,6 +240,11 @@ + + if (world instanceof ServerLevel worldserver) { + if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) { ++ // CraftBukkit start - call EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, this.getFluidState(state).createLegacyBlock())) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(blockposition, (BlockState) state.setValue(DecoratedPotBlock.CRACKED, true), 4); + world.destroyBlock(blockposition, true, projectile); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch new file mode 100644 index 0000000000..de7bae1c90 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -26,6 +26,7 @@ + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.level.redstone.Orientation; + import net.minecraft.world.phys.AABB; ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + + public class DetectorRailBlock extends BaseRailBlock { + +@@ -51,6 +52,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { + this.checkPressed(world, pos, state); +@@ -77,6 +79,7 @@ + + private void checkPressed(Level world, BlockPos pos, BlockState state) { + if (this.canSurvive(state, world, pos)) { ++ if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly + boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); + boolean flag1 = false; + List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> { +@@ -88,7 +91,17 @@ + } + + BlockState iblockdata1; ++ // CraftBukkit start ++ if (flag != flag1) { ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, flag ? 15 : 0, flag1 ? 15 : 0); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ } ++ // CraftBukkit end ++ + if (flag1 && !flag) { + iblockdata1 = (BlockState) state.setValue(DetectorRailBlock.POWERED, true); + world.setBlock(pos, iblockdata1, 3); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch new file mode 100644 index 0000000000..91c2eb8ada --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/level/block/DiodeBlock.java ++++ b/net/minecraft/world/level/block/DiodeBlock.java +@@ -23,6 +23,7 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.ticks.TickPriority; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public abstract class DiodeBlock extends HorizontalDirectionalBlock { + +@@ -59,8 +60,18 @@ + boolean flag1 = this.shouldTurnOn(world, pos, state); + + if (flag && !flag1) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(DiodeBlock.POWERED, false), 2); + } else if (!flag) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(DiodeBlock.POWERED, true), 2); + if (!flag1) { + world.scheduleTick(pos, (Block) this, this.getDelay(state), TickPriority.VERY_HIGH); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch new file mode 100644 index 0000000000..b394c4adfa --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/DirtPathBlock.java ++++ b/net/minecraft/world/level/block/DirtPathBlock.java +@@ -51,6 +51,11 @@ + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ // CraftBukkit start - do not fade if the block is valid here ++ if (state.canSurvive(world, pos)) { ++ return; ++ } ++ // CraftBukkit end + FarmBlock.turnToDirt((Entity) null, state, world, pos); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch new file mode 100644 index 0000000000..71019ffd00 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/world/level/block/DispenserBlock.java ++++ b/net/minecraft/world/level/block/DispenserBlock.java +@@ -52,6 +52,7 @@ + private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); + public static final Map DISPENSER_REGISTRY = new IdentityHashMap(); + private static final int TRIGGER_DURATION = 4; ++ public static boolean eventFired = false; // CraftBukkit + + @Override + public MapCodec codec() { +@@ -79,8 +80,9 @@ + if (tileentity instanceof DispenserBlockEntity) { + DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) tileentity; + +- player.openMenu(tileentitydispenser); ++ if (player.openMenu(tileentitydispenser).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(tileentitydispenser instanceof DropperBlockEntity ? Stats.INSPECT_DROPPER : Stats.INSPECT_DISPENSER); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + } + +@@ -88,7 +90,7 @@ + } + + public void dispenseFrom(ServerLevel world, BlockState state, BlockPos pos) { +- DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DISPENSER).orElse((Object) null); ++ DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DISPENSER).orElse(null); // CraftBukkit - decompile error + + if (tileentitydispenser == null) { + DispenserBlock.LOGGER.warn("Ignoring dispensing attempt for Dispenser without matching block entity at {}", pos); +@@ -97,13 +99,17 @@ + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + world.gameEvent((Holder) GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState())); ++ } // Paper - Add BlockFailedDispenseEvent + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack); + + if (idispensebehavior != DispenseItemBehavior.NOOP) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent ++ DispenserBlock.eventFired = false; // CraftBukkit - reset event status + tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); + } + +@@ -111,6 +117,12 @@ + } + } + ++ // Paper start - Fix NPE with equippable and items without behavior ++ public static DispenseItemBehavior getDispenseBehavior(BlockSource pointer, ItemStack stack) { ++ return ((DispenserBlock) pointer.state().getBlock()).getDispenseMethod(pointer.level(), stack); ++ } ++ // Paper end - Fix NPE with equippable and items without behavior ++ + protected DispenseItemBehavior getDispenseMethod(Level world, ItemStack stack) { + if (!stack.isItemEnabled(world.enabledFeatures())) { + return DispenserBlock.DEFAULT_BEHAVIOR; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch new file mode 100644 index 0000000000..ea278f700f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/block/DoorBlock.java ++++ b/net/minecraft/world/level/block/DoorBlock.java +@@ -38,6 +38,7 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + + public class DoorBlock extends Block { + +@@ -222,9 +223,24 @@ + + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { +- boolean flag1 = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); ++ // CraftBukkit start ++ BlockPos otherHalf = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN); + +- if (!this.defaultBlockState().is(sourceBlock) && flag1 != (Boolean) state.getValue(DoorBlock.POWERED)) { ++ org.bukkit.World bworld = world.getWorld(); ++ org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block blockTop = bworld.getBlockAt(otherHalf.getX(), otherHalf.getY(), otherHalf.getZ()); ++ ++ int power = bukkitBlock.getBlockPower(); ++ int powerTop = blockTop.getBlockPower(); ++ if (powerTop > power) power = powerTop; ++ int oldPower = (Boolean) state.getValue(DoorBlock.POWERED) ? 15 : 0; ++ ++ if (oldPower == 0 ^ power == 0) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, oldPower, power); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ boolean flag1 = eventRedstone.getNewCurrent() > 0; ++ // CraftBukkit end + if (flag1 != (Boolean) state.getValue(DoorBlock.OPEN)) { + this.playSound((Entity) null, world, pos, flag1); + world.gameEvent((Entity) null, (Holder) (flag1 ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE), pos); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch new file mode 100644 index 0000000000..657fe3fb50 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/DoubleBlockCombiner.java ++++ b/net/minecraft/world/level/block/DoubleBlockCombiner.java +@@ -34,7 +34,12 @@ + return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); + } else { + BlockPos blockPos = pos.relative(directionMapper.apply(state)); +- BlockState blockState = world.getBlockState(blockPos); ++ // Paper start - Don't load Chunks from Hoppers and other things ++ BlockState blockState = world.getBlockStateIfLoaded(blockPos); ++ if (blockState == null) { ++ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); ++ } ++ // Paper end - Don't load Chunks from Hoppers and other things + if (blockState.is(state.getBlock())) { + DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState); + if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch new file mode 100644 index 0000000000..a59d6fc4f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -98,11 +98,16 @@ + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + } + + protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { ++ // CraftBukkit start ++ if (((net.minecraft.server.level.ServerLevel)world).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { // Paper ++ return; ++ } ++ // CraftBukkit end + DoubleBlockHalf blockpropertydoubleblockhalf = (DoubleBlockHalf) state.getValue(DoublePlantBlock.HALF); + + if (blockpropertydoubleblockhalf == DoubleBlockHalf.UPPER) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch new file mode 100644 index 0000000000..b73b669baf --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/level/block/DragonEggBlock.java ++++ b/net/minecraft/world/level/block/DragonEggBlock.java +@@ -15,6 +15,7 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.BlockFromToEvent; // CraftBukkit + + public class DragonEggBlock extends FallingBlock { + +@@ -53,6 +54,18 @@ + BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); + + if (world.getBlockState(blockposition1).isAir() && worldborder.isWithinBounds(blockposition1)) { ++ // CraftBukkit start ++ org.bukkit.block.Block from = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ org.bukkit.block.Block to = world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ()); ++ BlockFromToEvent event = new BlockFromToEvent(from, to); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ blockposition1 = new BlockPos(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ()); ++ // CraftBukkit end + if (world.isClientSide) { + for (int j = 0; j < 128; ++j) { + double d0 = world.random.nextDouble(); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch new file mode 100644 index 0000000000..4d0a4875d4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/block/DropExperienceBlock.java ++++ b/net/minecraft/world/level/block/DropExperienceBlock.java +@@ -31,9 +31,16 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- this.tryDropExperience(world, pos, tool, this.xpRange); ++ // CraftBukkit start - Delegate to getExpDrop ++ } ++ ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange); + } + ++ return 0; ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch new file mode 100644 index 0000000000..0a8d6b5e2e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch @@ -0,0 +1,75 @@ +--- a/net/minecraft/world/level/block/DropperBlock.java ++++ b/net/minecraft/world/level/block/DropperBlock.java +@@ -8,6 +8,7 @@ + import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; + import net.minecraft.core.dispenser.DispenseItemBehavior; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.CompoundContainer; + import net.minecraft.world.Container; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.Level; +@@ -19,12 +20,15 @@ + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++// CraftBukkit end + + public class DropperBlock extends DispenserBlock { + + private static final Logger LOGGER = LogUtils.getLogger(); + public static final MapCodec CODEC = simpleCodec(DropperBlock::new); +- private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior(); ++ private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior(true); // CraftBukkit + + @Override + public MapCodec codec() { +@@ -47,7 +51,7 @@ + + @Override + public void dispenseFrom(ServerLevel world, BlockState state, BlockPos pos) { +- DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DROPPER).orElse((Object) null); ++ DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DROPPER).orElse(null); // CraftBukkit - decompile error + + if (tileentitydispenser == null) { + DropperBlock.LOGGER.warn("Ignoring dispensing attempt for Dropper without matching block entity at {}", pos); +@@ -56,6 +60,7 @@ + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +@@ -66,10 +71,28 @@ + ItemStack itemstack1; + + if (iinventory == null) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent + itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack); + } else { +- itemstack1 = HopperBlockEntity.addItem(tileentitydispenser, iinventory, itemstack.copyWithCount(1), enumdirection.getOpposite()); +- if (itemstack1.isEmpty()) { ++ // CraftBukkit start - Fire event when pushing items into other inventories ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack.copyWithCount(1)); ++ ++ org.bukkit.inventory.Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentitydispenser.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ itemstack1 = HopperBlockEntity.addItem(tileentitydispenser, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection.getOpposite()); ++ if (event.getItem().equals(oitemstack) && itemstack1.isEmpty()) { ++ // CraftBukkit end + itemstack1 = itemstack.copy(); + itemstack1.shrink(1); + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch new file mode 100644 index 0000000000..2e2055c634 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/EndGatewayBlock.java ++++ b/net/minecraft/world/level/block/EndGatewayBlock.java +@@ -22,6 +22,9 @@ + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.player.PlayerTeleportEvent; ++// CraftBukkit end + + public class EndGatewayBlock extends BaseEntityBlock implements Portal { + +@@ -89,7 +92,12 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { ++ // Paper start - call EntityPortalEnterEvent ++ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type ++ if (!event.callEvent()) return; ++ // Paper end - call EntityPortalEnterEvent + BlockEntity tileentity = world.getBlockEntity(pos); + + if (!world.isClientSide && tileentity instanceof TheEndGatewayBlockEntity) { +@@ -112,7 +120,7 @@ + if (tileentity instanceof TheEndGatewayBlockEntity tileentityendgateway) { + Vec3 vec3d = tileentityendgateway.getPortalPosition(world, pos); + +- return vec3d == null ? null : (entity instanceof ThrownEnderpearl ? new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET) : new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET)); ++ return vec3d == null ? null : (entity instanceof ThrownEnderpearl ? new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, PlayerTeleportEvent.TeleportCause.END_GATEWAY) : new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, PlayerTeleportEvent.TeleportCause.END_GATEWAY)); // CraftBukkit + } else { + return null; + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch new file mode 100644 index 0000000000..c075ce1cd8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch @@ -0,0 +1,91 @@ +--- a/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/net/minecraft/world/level/block/EndPortalBlock.java +@@ -19,12 +19,23 @@ + import net.minecraft.world.level.block.entity.TheEndPortalBlockEntity; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.levelgen.feature.EndPlatformFeature; + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.entity.EntityPortalEnterEvent; ++import org.bukkit.event.player.PlayerRespawnEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++// CraftBukkit end + + public class EndPortalBlock extends BaseEntityBlock implements Portal { + +@@ -57,10 +68,17 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { ++ // CraftBukkit start - Entity in portal ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) return; // Paper - make cancellable ++ // CraftBukkit end + if (!world.isClientSide && world.dimension() == Level.END && entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + ++ if (world.paperConfig().misc.disableEndCredits) entityplayer.seenCredits = true; // Paper - Option to disable end credits + if (!entityplayer.seenCredits) { + entityplayer.showEndCredits(); + return; +@@ -74,11 +92,11 @@ + + @Override + public TeleportTransition getPortalDestination(ServerLevel world, Entity entity, BlockPos pos) { +- ResourceKey resourcekey = world.dimension() == Level.END ? Level.OVERWORLD : Level.END; ++ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends + ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); + + if (worldserver1 == null) { +- return null; ++ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist + } else { + boolean flag = resourcekey == Level.END; + BlockPos blockposition1 = flag ? ServerLevel.END_SPAWN_POINT : worldserver1.getSharedSpawnPos(); +@@ -87,7 +105,7 @@ + Set set; + + if (flag) { +- EndPlatformFeature.createEndPlatform(worldserver1, BlockPos.containing(vec3d).below(), true); ++ EndPlatformFeature.createEndPlatform(worldserver1, BlockPos.containing(vec3d).below(), true, entity); // CraftBukkit + f = Direction.WEST.toYRot(); + set = Relative.union(Relative.DELTA, Set.of(Relative.X_ROT)); + if (entity instanceof ServerPlayer) { +@@ -99,13 +117,21 @@ + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +- return entityplayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING); ++ return entityplayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING, PlayerRespawnEvent.RespawnReason.END_PORTAL); // CraftBukkit + } + + vec3d = entity.adjustSpawnLocation(worldserver1, blockposition1).getBottomCenter(); + } + +- return new TeleportTransition(worldserver1, vec3d, Vec3.ZERO, f, 0.0F, set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET)); ++ // CraftBukkit start ++ CraftPortalEvent event = entity.callPortalEvent(entity, CraftLocation.toBukkit(vec3d, worldserver1.getWorld(), f, entity.getXRot()), PlayerTeleportEvent.TeleportCause.END_PORTAL, 0, 0); ++ if (event == null) { ++ return null; ++ } ++ Location to = event.getTo(); ++ ++ return new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), entity.getDeltaMovement(), to.getYaw(), to.getPitch(), set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), PlayerTeleportEvent.TeleportCause.END_PORTAL); ++ // CraftBukkit end + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch new file mode 100644 index 0000000000..0b1e52d838 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/EnderChestBlock.java ++++ b/net/minecraft/world/level/block/EnderChestBlock.java +@@ -78,14 +78,16 @@ + PlayerEnderChestContainer playerEnderChestContainer = player.getEnderChestInventory(); + if (playerEnderChestContainer != null && world.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) { + BlockPos blockPos = pos.above(); +- if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) { ++ if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic + return InteractionResult.SUCCESS; + } else { +- if (world instanceof ServerLevel serverLevel) { +- playerEnderChestContainer.setActiveChest(enderChestBlockEntity); +- player.openMenu( +- new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) +- ); ++ // Paper start - Fix InventoryOpenEvent cancellation - moved up; ++ playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations ++ if (world instanceof ServerLevel serverLevel && player.openMenu( ++ new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) ++ ).isPresent()) { ++ // Paper end - Fix InventoryOpenEvent cancellation - moved up; ++ // Paper - Fix InventoryOpenEvent cancellation - moved up; + player.awardStat(Stats.OPEN_ENDERCHEST); + PiglinAi.angerNearbyPiglins(serverLevel, player, true); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch new file mode 100644 index 0000000000..0f677874ee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/EyeblossomBlock.java ++++ b/net/minecraft/world/level/block/EyeblossomBlock.java +@@ -100,6 +100,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide() + && world.getDifficulty() != Difficulty.PEACEFUL + && entity instanceof Bee bee diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch new file mode 100644 index 0000000000..d862f84ba3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch @@ -0,0 +1,112 @@ +--- a/net/minecraft/world/level/block/FarmBlock.java ++++ b/net/minecraft/world/level/block/FarmBlock.java +@@ -29,6 +29,10 @@ + import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityInteractEvent; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class FarmBlock extends Block { + +@@ -89,31 +93,56 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + int i = (Integer) state.getValue(FarmBlock.MOISTURE); ++ if (i > 0 && world.paperConfig().tickRates.wetFarmland != 1 && (world.paperConfig().tickRates.wetFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks ++ if (i == 0 && world.paperConfig().tickRates.dryFarmland != 1 && (world.paperConfig().tickRates.dryFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks + + if (!FarmBlock.isNearWater(world, pos) && !world.isRainingAt(pos.above())) { + if (i > 0) { +- world.setBlock(pos, (BlockState) state.setValue(FarmBlock.MOISTURE, i - 1), 2); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(world, pos, (BlockState) state.setValue(FarmBlock.MOISTURE, i - 1), 2); // CraftBukkit + } else if (!FarmBlock.shouldMaintainFarmland(world, pos)) { + FarmBlock.turnToDirt((Entity) null, state, world, pos); + } + } else if (i < 7) { +- world.setBlock(pos, (BlockState) state.setValue(FarmBlock.MOISTURE, 7), 2); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(world, pos, (BlockState) state.setValue(FarmBlock.MOISTURE, 7), 2); // CraftBukkit + } + + } + + @Override + public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { ++ super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. + if (world instanceof ServerLevel worldserver) { + if (world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { ++ // CraftBukkit start - Interact soil ++ org.bukkit.event.Cancellable cancellable; ++ if (entity instanceof Player) { ++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ if (cancellable.isCancelled()) { ++ return; ++ } ++ ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { ++ return; ++ } ++ // CraftBukkit end + FarmBlock.turnToDirt(entity, state, world, pos); + } + } + +- super.fallOn(world, state, pos, entity, fallDistance); ++ // super.fallOn(world, iblockdata, blockposition, entity, f); // CraftBukkit - moved up + } + + public static void turnToDirt(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { ++ // CraftBukkit start ++ if (CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + BlockState iblockdata1 = pushEntitiesUp(state, Blocks.DIRT.defaultBlockState(), world, pos); + + world.setBlockAndUpdate(pos, iblockdata1); +@@ -125,19 +154,28 @@ + } + + private static boolean isNearWater(LevelReader world, BlockPos pos) { +- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); ++ // Paper start - Perf: remove abstract block iteration ++ int xOff = pos.getX(); ++ int yOff = pos.getY(); ++ int zOff = pos.getZ(); + +- BlockPos blockposition1; +- +- do { +- if (!iterator.hasNext()) { +- return false; ++ for (int dz = -4; dz <= 4; ++dz) { ++ int z = dz + zOff; ++ for (int dx = -4; dx <= 4; ++dx) { ++ int x = xOff + dx; ++ for (int dy = 0; dy <= 1; ++dy) { ++ int y = dy + yOff; ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); ++ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState(); ++ if (fluid.is(FluidTags.WATER)) { ++ return true; ++ } ++ } + } ++ } + +- blockposition1 = (BlockPos) iterator.next(); +- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER)); +- +- return true; ++ return false; ++ // Paper end - Perf: remove abstract block iteration + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch new file mode 100644 index 0000000000..78872a0616 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/block/FenceGateBlock.java ++++ b/net/minecraft/world/level/block/FenceGateBlock.java +@@ -173,6 +173,17 @@ + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { + if (!world.isClientSide) { + boolean flag1 = world.hasNeighborSignal(pos); ++ // CraftBukkit start ++ boolean oldPowered = state.getValue(FenceGateBlock.POWERED); ++ if (oldPowered != flag1) { ++ int newPower = flag1 ? 15 : 0; ++ int oldPower = oldPowered ? 15 : 0; ++ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, newPower); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ } ++ // CraftBukkit end + + if ((Boolean) state.getValue(FenceGateBlock.POWERED) != flag1) { + world.setBlock(pos, (BlockState) ((BlockState) state.setValue(FenceGateBlock.POWERED, flag1)).setValue(FenceGateBlock.OPEN, flag1), 2); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch new file mode 100644 index 0000000000..5d840efc2e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch @@ -0,0 +1,205 @@ +--- a/net/minecraft/world/level/block/FireBlock.java ++++ b/net/minecraft/world/level/block/FireBlock.java +@@ -14,6 +14,7 @@ + import net.minecraft.tags.BiomeTags; + import net.minecraft.util.RandomSource; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; +@@ -28,6 +29,12 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockStates; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.block.BlockBurnEvent; ++import org.bukkit.event.block.BlockFadeEvent; ++// CraftBukkit end + + public class FireBlock extends BaseFireBlock { + +@@ -100,7 +107,25 @@ + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { +- return this.canSurvive(state, world, pos) ? this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); ++ // CraftBukkit start ++ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation ++ if (!this.canSurvive(state, world, pos)) { ++ // Suppress during worldgen ++ if (!(world instanceof Level world1)) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ CraftBlockState blockState = CraftBlockStates.getBlockState(world1, pos); ++ blockState.setData(Blocks.AIR.defaultBlockState()); ++ ++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState); ++ world1.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ return blockState.getHandle(); ++ } ++ } ++ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation" ++ // CraftBukkit end + } + + @Override +@@ -146,10 +171,10 @@ + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); ++ world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option + if (world.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { + if (!state.canSurvive(world, pos)) { +- world.removeBlock(pos, false); ++ this.fireExtinguished(world, pos); // CraftBukkit - invalid place location + } + + BlockState iblockdata1 = world.getBlockState(pos.below()); +@@ -157,7 +182,7 @@ + int i = (Integer) state.getValue(FireBlock.AGE); + + if (!flag && world.isRaining() && this.isNearRain(world, pos) && random.nextFloat() < 0.2F + (float) i * 0.03F) { +- world.removeBlock(pos, false); ++ this.fireExtinguished(world, pos); // CraftBukkit - extinguished by rain + } else { + int j = Math.min(15, i + random.nextInt(3) / 2); + +@@ -171,14 +196,14 @@ + BlockPos blockposition1 = pos.below(); + + if (!world.getBlockState(blockposition1).isFaceSturdy(world, blockposition1, Direction.UP) || i > 3) { +- world.removeBlock(pos, false); ++ this.fireExtinguished(world, pos); // CraftBukkit + } + + return; + } + + if (i == 15 && random.nextInt(4) == 0 && !this.canBurn(world.getBlockState(pos.below()))) { +- world.removeBlock(pos, false); ++ this.fireExtinguished(world, pos); // CraftBukkit + return; + } + } +@@ -186,12 +211,14 @@ + boolean flag1 = world.getBiome(pos).is(BiomeTags.INCREASED_FIRE_BURNOUT); + int k = flag1 ? -50 : 0; + +- this.checkBurnOut(world, pos.east(), 300 + k, random, i); +- this.checkBurnOut(world, pos.west(), 300 + k, random, i); +- this.checkBurnOut(world, pos.below(), 250 + k, random, i); +- this.checkBurnOut(world, pos.above(), 250 + k, random, i); +- this.checkBurnOut(world, pos.north(), 300 + k, random, i); +- this.checkBurnOut(world, pos.south(), 300 + k, random, i); ++ // CraftBukkit start - add source blockposition to burn calls ++ this.trySpread(world, pos.east(), 300 + k, random, i, pos); ++ this.trySpread(world, pos.west(), 300 + k, random, i, pos); ++ this.trySpread(world, pos.below(), 250 + k, random, i, pos); ++ this.trySpread(world, pos.above(), 250 + k, random, i, pos); ++ this.trySpread(world, pos.north(), 300 + k, random, i, pos); ++ this.trySpread(world, pos.south(), 300 + k, random, i, pos); ++ // CraftBukkit end + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + + for (int l = -1; l <= 1; ++l) { +@@ -217,7 +244,15 @@ + if (i2 > 0 && random.nextInt(k1) <= i2 && (!world.isRaining() || !this.isNearRain(world, blockposition_mutableblockposition))) { + int j2 = Math.min(15, i + random.nextInt(5) / 4); + +- world.setBlock(blockposition_mutableblockposition, this.getStateWithAge(world, blockposition_mutableblockposition, j2), 3); ++ // CraftBukkit start - Call to stop spread of fire ++ if (world.getBlockState(blockposition_mutableblockposition).getBlock() != Blocks.FIRE) { ++ if (CraftEventFactory.callBlockIgniteEvent(world, blockposition_mutableblockposition, pos).isCancelled()) { ++ continue; ++ } ++ ++ CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition_mutableblockposition, this.getStateWithAge(world, blockposition_mutableblockposition, j2), 3); // CraftBukkit ++ } ++ // CraftBukkit end + } + } + } +@@ -241,24 +276,47 @@ + return state.hasProperty(BlockStateProperties.WATERLOGGED) && (Boolean) state.getValue(BlockStateProperties.WATERLOGGED) ? 0 : this.igniteOdds.getInt(state.getBlock()); + } + +- private void checkBurnOut(Level world, BlockPos pos, int spreadFactor, RandomSource random, int currentAge) { +- int k = this.getBurnOdds(world.getBlockState(pos)); ++ private void trySpread(Level world, BlockPos blockposition, int i, RandomSource randomsource, int j, BlockPos sourceposition) { // CraftBukkit add sourceposition ++ int k = this.getBurnOdds(world.getBlockState(blockposition)); + +- if (random.nextInt(spreadFactor) < k) { +- BlockState iblockdata = world.getBlockState(pos); ++ if (randomsource.nextInt(i) < k) { ++ BlockState iblockdata = world.getBlockState(blockposition); + +- if (random.nextInt(currentAge + 10) < 5 && !world.isRainingAt(pos)) { +- int l = Math.min(currentAge + random.nextInt(5) / 4, 15); ++ // CraftBukkit start ++ org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ org.bukkit.block.Block sourceBlock = world.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ()); + +- world.setBlock(pos, this.getStateWithAge(world, pos, l), 3); ++ BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ if (iblockdata.getBlock() instanceof TntBlock && !CraftEventFactory.callTNTPrimeEvent(world, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) { ++ return; ++ } ++ // CraftBukkit end ++ ++ if (randomsource.nextInt(j + 10) < 5 && !world.isRainingAt(blockposition)) { ++ int l = Math.min(j + randomsource.nextInt(5) / 4, 15); ++ ++ world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3); + } else { +- world.removeBlock(pos, false); ++ if(iblockdata.getBlock() != Blocks.TNT) world.removeBlock(blockposition, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down + } + + Block block = iblockdata.getBlock(); + + if (block instanceof TntBlock) { +- TntBlock.explode(world, pos); ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) { ++ return; ++ } ++ world.removeBlock(blockposition, false); ++ // Paper end - TNTPrimeEvent ++ TntBlock.explode(world, blockposition); + } + } + +@@ -310,13 +368,15 @@ + } + + @Override +- protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- super.onPlace(state, world, pos, oldState, notify); +- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); ++ // CraftBukkit start - context ++ protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) { ++ super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, context); ++ // CraftBukkit end ++ world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option + } + +- private static int getFireTickDelay(RandomSource random) { +- return 30 + random.nextInt(10); ++ private static int getFireTickDelay(Level world) { // Paper - Add fire-tick-delay option ++ return world.paperConfig().environment.fireTickDelay + world.random.nextInt(10); // Paper - Add fire-tick-delay option + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch new file mode 100644 index 0000000000..a2ee2e43ae --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch @@ -0,0 +1,40 @@ +--- a/net/minecraft/world/level/block/FlowerPotBlock.java ++++ b/net/minecraft/world/level/block/FlowerPotBlock.java +@@ -63,6 +63,18 @@ + } else if (!this.isEmpty()) { + return InteractionResult.CONSUME; + } else { ++ // Paper start - Add PlayerFlowerPotManipulateEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack placedStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack); ++ ++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, placedStack, true); ++ if (!event.callEvent()) { ++ // Update client ++ player.containerMenu.sendAllDataToRemote(); ++ ++ return InteractionResult.CONSUME; ++ } ++ // Paper end - Add PlayerFlowerPotManipulateEvent + world.setBlock(pos, blockState, 3); + world.gameEvent(player, GameEvent.BLOCK_CHANGE, pos); + player.awardStat(Stats.POT_FLOWER); +@@ -77,6 +89,18 @@ + return InteractionResult.CONSUME; + } else { + ItemStack itemStack = new ItemStack(this.potted); ++ // Paper start - Add PlayerFlowerPotManipulateEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack pottedStack = new org.bukkit.inventory.ItemStack(org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.potted)); ++ ++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, pottedStack, false); ++ if (!event.callEvent()) { ++ // Update client ++ player.containerMenu.sendAllDataToRemote(); ++ ++ return InteractionResult.PASS; ++ } ++ // Paper end - Add PlayerFlowerPotManipulateEvent + if (!player.addItem(itemStack)) { + player.drop(itemStack, false); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch new file mode 100644 index 0000000000..c5fefd2f6b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -89,6 +89,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.getType().equals(EntityType.FALLING_BLOCK)) { + this.destroyBlock(world, pos); + } +@@ -101,6 +102,11 @@ + } + + private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) { ++ // Paper start - Call BlockFadeEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // Paper end - Call BlockFadeEvent + this.destroyBlock(world, pos); + world.playSound(null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F); + this.spawnTadpoles(world, pos, random); +@@ -121,7 +127,7 @@ + int k = random.nextInt(1, 361); + tadpole.moveTo(d, (double)pos.getY() - 0.5, e, (float)k, 0.0F); + tadpole.setPersistenceRequired(); +- world.addFreshEntity(tadpole); ++ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch new file mode 100644 index 0000000000..9d1efc3454 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/level/block/FrostedIceBlock.java ++++ b/net/minecraft/world/level/block/FrostedIceBlock.java +@@ -42,6 +42,7 @@ + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (!world.paperConfig().environment.frostedIce.enabled) return; // Paper - Frosted ice options + if ((random.nextInt(3) == 0 || this.fewerNeigboursThan(world, pos, 4)) + && world.getMaxLocalRawBrightness(pos) > 11 - state.getValue(AGE) - state.getLightBlock() + && this.slightlyMelt(state, world, pos)) { +@@ -51,11 +52,11 @@ + mutableBlockPos.setWithOffset(pos, direction); + BlockState blockState = world.getBlockState(mutableBlockPos); + if (blockState.is(this) && !this.slightlyMelt(blockState, world, mutableBlockPos)) { +- world.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, 20, 40)); ++ world.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, world.paperConfig().environment.frostedIce.delay.min, world.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options + } + } + } else { +- world.scheduleTick(pos, this, Mth.nextInt(random, 20, 40)); ++ world.scheduleTick(pos, this, Mth.nextInt(random, world.paperConfig().environment.frostedIce.delay.min, world.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch new file mode 100644 index 0000000000..6508c8df14 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -74,6 +74,13 @@ + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + this.getFeature(world).ifPresent((holder) -> { ++ // CraftBukkit start ++ if (this == Blocks.WARPED_FUNGUS) { ++ SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ } else if (this == Blocks.CRIMSON_FUNGUS) { ++ SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ } ++ // CraftBukkit end + ((ConfiguredFeature) holder.value()).place(world, world.getChunkSource().getGenerator(), random, pos); + }); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch new file mode 100644 index 0000000000..ffebc4893d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/FurnaceBlock.java ++++ b/net/minecraft/world/level/block/FurnaceBlock.java +@@ -45,8 +45,7 @@ + @Override + protected void openContainer(Level world, BlockPos pos, Player player) { + BlockEntity blockEntity = world.getBlockEntity(pos); +- if (blockEntity instanceof FurnaceBlockEntity) { +- player.openMenu((MenuProvider)blockEntity); ++ if (blockEntity instanceof FurnaceBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_FURNACE); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch new file mode 100644 index 0000000000..860a309048 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/GrindstoneBlock.java ++++ b/net/minecraft/world/level/block/GrindstoneBlock.java +@@ -152,8 +152,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_GRINDSTONE); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch new file mode 100644 index 0000000000..b55ee5aef9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java ++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +@@ -44,16 +44,34 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < this.growPerTickProbability) { ++ // Spigot start ++ int modifier; ++ if (this == Blocks.KELP) { ++ modifier = world.spigotConfig.kelpModifier; ++ } else if (this == Blocks.TWISTING_VINES) { ++ modifier = world.spigotConfig.twistingVinesModifier; ++ } else if (this == Blocks.WEEPING_VINES) { ++ modifier = world.spigotConfig.weepingVinesModifier; ++ } else { ++ modifier = world.spigotConfig.caveVinesModifier; ++ } ++ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution ++ // Spigot end + BlockPos blockposition1 = pos.relative(this.growthDirection); + + if (this.canGrowInto(world.getBlockState(blockposition1))) { +- world.setBlockAndUpdate(blockposition1, this.getGrowIntoState(state, world.random)); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - Fix Spigot growth modifiers + } + } + + } + ++ // Paper start - Fix Spigot growth modifiers ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ return this.getGrowIntoState(state, random); ++ } ++ // Paper end - Fix Spigot growth modifiers ++ + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { + return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch new file mode 100644 index 0000000000..4609ae3eee --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/HoneyBlock.java ++++ b/net/minecraft/world/level/block/HoneyBlock.java +@@ -60,6 +60,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isSlidingDown(pos, entity)) { + this.maybeDoSlideAchievement(entity, pos); + this.doSlideMovement(entity); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch new file mode 100644 index 0000000000..3e829e33d0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/block/HopperBlock.java ++++ b/net/minecraft/world/level/block/HopperBlock.java +@@ -125,8 +125,7 @@ + + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { +- if (!world.isClientSide && world.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity) { +- player.openMenu(hopperBlockEntity); ++ if (!world.isClientSide && world.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity && player.openMenu(hopperBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INSPECT_HOPPER); + } + +@@ -178,6 +177,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof HopperBlockEntity) { + HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch new file mode 100644 index 0000000000..fb60ae21d7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/HugeMushroomBlock.java ++++ b/net/minecraft/world/level/block/HugeMushroomBlock.java +@@ -45,6 +45,7 @@ + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates + BlockGetter blockGetter = ctx.getLevel(); + BlockPos blockPos = ctx.getClickedPos(); + return this.defaultBlockState() +@@ -67,6 +68,7 @@ + BlockState neighborState, + RandomSource random + ) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return neighborState.is(this) + ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(false)) + : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -74,6 +76,7 @@ + + @Override + protected BlockState rotate(BlockState state, Rotation rotation) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return state.setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH)) + .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH)) + .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST)) +@@ -84,6 +87,7 @@ + + @Override + protected BlockState mirror(BlockState state, Mirror mirror) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return state.setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.NORTH)), state.getValue(NORTH)) + .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.SOUTH)), state.getValue(SOUTH)) + .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.EAST)), state.getValue(EAST)) diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch new file mode 100644 index 0000000000..003b651052 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/level/block/IceBlock.java ++++ b/net/minecraft/world/level/block/IceBlock.java +@@ -34,8 +34,13 @@ + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion ++ // Paper start - Improve Block#breakNaturally API ++ this.afterDestroy(world, pos, tool); ++ } ++ public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { ++ // Paper end - Improve Block#breakNaturally API + if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { + if (world.dimensionType().ultraWarm()) { + world.removeBlock(pos, false); +@@ -60,6 +65,11 @@ + } + + protected void melt(BlockState state, Level world, BlockPos pos) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, world.dimensionType().ultraWarm() ? Blocks.AIR.defaultBlockState() : Blocks.WATER.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + if (world.dimensionType().ultraWarm()) { + world.removeBlock(pos, false); + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch new file mode 100644 index 0000000000..49d54e7dd2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/InfestedBlock.java ++++ b/net/minecraft/world/level/block/InfestedBlock.java +@@ -19,6 +19,7 @@ + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.Property; ++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit + + public class InfestedBlock extends Block { + +@@ -54,7 +55,7 @@ + + if (entitysilverfish != null) { + entitysilverfish.moveTo((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, 0.0F, 0.0F); +- world.addFreshEntity(entitysilverfish); ++ world.addFreshEntity(entitysilverfish, SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason + entitysilverfish.spawnAnim(); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch new file mode 100644 index 0000000000..b8db542f5e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/LavaCauldronBlock.java ++++ b/net/minecraft/world/level/block/LavaCauldronBlock.java +@@ -32,6 +32,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isEntityInsideContent(state, pos, entity)) { + entity.lavaHurt(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch new file mode 100644 index 0000000000..67743248ac --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch @@ -0,0 +1,127 @@ +--- a/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -17,6 +17,11 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.material.Fluid; + import net.minecraft.world.level.material.Fluids; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.block.CraftBlockStates; ++import org.bukkit.event.block.CauldronLevelChangeEvent; ++// CraftBukkit end + + public class LayeredCauldronBlock extends AbstractCauldronBlock { + +@@ -62,41 +67,86 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { + if (entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { +- entity.clearFire(); +- if (entity.mayInteract(worldserver, pos)) { +- this.handleEntityOnFireInside(state, world, pos); ++ // CraftBukkit start - moved down ++ // entity.clearFire(); ++ if ((entity instanceof net.minecraft.world.entity.player.Player || worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(worldserver, pos)) { // Paper - Fixes MC-248588 ++ if (this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities ++ entity.clearFire(); ++ } ++ // CraftBukkit end + } + } + } + + } + +- private void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { ++ // CraftBukkit start ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent ++ private boolean handleEntityOnFireInside(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) { + if (this.precipitationType == Biome.Precipitation.SNOW) { +- LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos); ++ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL)), world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); + } else { +- LayeredCauldronBlock.lowerFillLevel(state, world, pos); ++ return LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ // CraftBukkit end + } + + } ++ // Paper start - fix powdered snow cauldron extinguishing entities ++ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (this.precipitationType == Biome.Precipitation.SNOW) { ++ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ } else { ++ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ } ++ } ++ // Paper end - fix powdered snow cauldron extinguishing entities + + public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) { +- int i = (Integer) state.getValue(LayeredCauldronBlock.LEVEL) - 1; +- BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, i); ++ // CraftBukkit start ++ LayeredCauldronBlock.lowerFillLevel(state, world, pos, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN); ++ } + +- world.setBlockAndUpdate(pos, iblockdata1); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); ++ public static boolean lowerFillLevel(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { ++ int i = (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) - 1; ++ BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) iblockdata.setValue(LayeredCauldronBlock.LEVEL, i); ++ ++ return LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata1, entity, reason); + } + ++ // CraftBukkit start ++ // Paper start - Call CauldronLevelChangeEvent ++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable ++ return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true); ++ } ++ ++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable ++ // Paper end - Call CauldronLevelChangeEvent ++ CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition); ++ newState.setData(newBlock); ++ ++ CauldronLevelChangeEvent event = new CauldronLevelChangeEvent( ++ world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), ++ (entity == null) ? null : entity.getBukkitEntity(), reason, newState ++ ); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ newState.update(true); ++ if (sendGameEvent) world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent ++ return true; ++ } ++ // CraftBukkit end ++ + @Override + public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { + if (CauldronBlock.shouldHandlePrecipitation(world, precipitation) && (Integer) state.getValue(LayeredCauldronBlock.LEVEL) != 3 && precipitation == this.precipitationType) { + BlockState iblockdata1 = (BlockState) state.cycle(LayeredCauldronBlock.LEVEL); + +- world.setBlockAndUpdate(pos, iblockdata1); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); ++ LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit + } + } + +@@ -115,8 +165,11 @@ + if (!this.isFull(state)) { + BlockState iblockdata1 = (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL) + 1); + +- world.setBlockAndUpdate(pos, iblockdata1); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); ++ // CraftBukkit start ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { ++ return; ++ } ++ // CraftBukkit end + world.levelEvent(1047, pos, 0); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch new file mode 100644 index 0000000000..4e3cc600f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/LeavesBlock.java ++++ b/net/minecraft/world/level/block/LeavesBlock.java +@@ -26,6 +26,7 @@ + import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.LeavesDecayEvent; // CraftBukkit + + public class LeavesBlock extends Block implements SimpleWaterloggedBlock { + +@@ -59,6 +60,14 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (this.decaying(state)) { ++ // CraftBukkit start ++ LeavesDecayEvent event = new LeavesDecayEvent(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || world.getBlockState(pos).getBlock() != this) { ++ return; ++ } ++ // CraftBukkit end + dropResources(state, world, pos); + world.removeBlock(pos, false); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch new file mode 100644 index 0000000000..a928fea703 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/world/level/block/LecternBlock.java ++++ b/net/minecraft/world/level/block/LecternBlock.java +@@ -153,7 +153,24 @@ + BlockEntity tileentity = world.getBlockEntity(pos); + + if (tileentity instanceof LecternBlockEntity tileentitylectern) { +- tileentitylectern.setBook(stack.consumeAndReturn(1, user)); ++ // Paper start - Add PlayerInsertLecternBookEvent ++ ItemStack eventSourcedBookStack = null; ++ if (user instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) { ++ final io.papermc.paper.event.player.PlayerInsertLecternBookEvent event = new io.papermc.paper.event.player.PlayerInsertLecternBookEvent( ++ serverPlayer.getBukkitEntity(), ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack.copyWithCount(1)) ++ ); ++ if (!event.callEvent()) return; ++ eventSourcedBookStack = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getBook()); ++ } ++ if (eventSourcedBookStack == null) { ++ eventSourcedBookStack = stack.consumeAndReturn(1, user); ++ } else { ++ stack.consume(1, user); ++ } ++ tileentitylectern.setBook(eventSourcedBookStack); ++ // Paper end - Add PlayerInsertLecternBookEvent + LecternBlock.resetBookState(user, world, pos, state, true); + world.playSound((Player) null, pos, SoundEvents.BOOK_PUT, SoundSource.BLOCKS, 1.0F, 1.0F); + } +@@ -175,6 +192,16 @@ + } + + private static void changePowered(Level world, BlockPos pos, BlockState state, boolean powered) { ++ // Paper start - Call BlockRedstoneEvent properly ++ final int currentRedstoneLevel = state.getValue(LecternBlock.POWERED) ? 15 : 0, targetRedstoneLevel = powered ? 15 : 0; ++ if (currentRedstoneLevel != targetRedstoneLevel) { ++ final org.bukkit.event.block.BlockRedstoneEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, currentRedstoneLevel, targetRedstoneLevel); ++ ++ if (event.getNewCurrent() != targetRedstoneLevel) { ++ return; ++ } ++ } ++ // Paper end - Call BlockRedstoneEvent properly + world.setBlock(pos, (BlockState) state.setValue(LecternBlock.POWERED, powered), 3); + LecternBlock.updateBelow(world, pos, state); + } +@@ -206,11 +233,12 @@ + } + + private void popBook(BlockState state, Level world, BlockPos pos) { +- BlockEntity tileentity = world.getBlockEntity(pos); ++ BlockEntity tileentity = world.getBlockEntity(pos, false); // CraftBukkit - don't validate, type may be changed already + + if (tileentity instanceof LecternBlockEntity tileentitylectern) { + Direction enumdirection = (Direction) state.getValue(LecternBlock.FACING); + ItemStack itemstack = tileentitylectern.getBook().copy(); ++ if (itemstack.isEmpty()) return; // CraftBukkit - SPIGOT-5500 + float f = 0.25F * (float) enumdirection.getStepX(); + float f1 = 0.25F * (float) enumdirection.getStepZ(); + ItemEntity entityitem = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) f, (double) (pos.getY() + 1), (double) pos.getZ() + 0.5D + (double) f1, itemstack); +@@ -282,8 +310,7 @@ + private void openScreen(Level world, BlockPos pos, Player player) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof LecternBlockEntity) { +- player.openMenu((LecternBlockEntity) tileentity); ++ if (tileentity instanceof LecternBlockEntity && player.openMenu((LecternBlockEntity) tileentity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_LECTERN); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch new file mode 100644 index 0000000000..325443e6f3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/level/block/LeverBlock.java ++++ b/net/minecraft/world/level/block/LeverBlock.java +@@ -31,6 +31,7 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + + public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock { + +@@ -102,6 +103,20 @@ + LeverBlock.makeParticle(iblockdata1, world, pos, 1.0F); + } + } else { ++ // CraftBukkit start - Interact Lever ++ boolean powered = state.getValue(LeverBlock.POWERED); // Old powered state ++ org.bukkit.block.Block block = world.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); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) { ++ return InteractionResult.SUCCESS; ++ } ++ // CraftBukkit end ++ + this.pull(state, world, pos, (Player) null); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch new file mode 100644 index 0000000000..4e34991e01 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/level/block/LightBlock.java ++++ b/net/minecraft/world/level/block/LightBlock.java +@@ -50,7 +50,15 @@ + builder.add(LEVEL, WATERLOGGED); + } + ++ // Paper start - prevent unintended light block manipulation + @Override ++ protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) { ++ if (player.getItemInHand(hand).getItem() != Items.LIGHT || (world instanceof final net.minecraft.server.level.ServerLevel serverLevel && !player.mayInteract(serverLevel, pos)) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return net.minecraft.world.InteractionResult.PASS; } // Paper - Prevent unintended light block manipulation ++ return super.useItemOn(stack, state, world, pos, player, hand, hit); ++ } ++ // Paper end - prevent unintended light block manipulation ++ ++ @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide && player.canUseGameMasterBlocks()) { + world.setBlock(pos, state.cycle(LEVEL), 2); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch new file mode 100644 index 0000000000..c97ad7d96c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/world/level/block/LightningRodBlock.java ++++ b/net/minecraft/world/level/block/LightningRodBlock.java +@@ -24,6 +24,11 @@ + import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++// CraftBukkit end ++ + public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBlock { + + public static final MapCodec CODEC = simpleCodec(LightningRodBlock::new); +@@ -76,6 +81,18 @@ + } + + public void onLightningStrike(BlockState state, Level world, BlockPos pos) { ++ // CraftBukkit start ++ boolean powered = state.getValue(LightningRodBlock.POWERED); ++ int old = (powered) ? 15 : 0; ++ int current = (!powered) ? 15 : 0; ++ ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), old, current); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() <= 0) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(LightningRodBlock.POWERED, true), 3); + this.updateNeighbours(state, world, pos); + world.scheduleTick(pos, (Block) this, 8); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch new file mode 100644 index 0000000000..42df446a82 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch @@ -0,0 +1,78 @@ +--- a/net/minecraft/world/level/block/LiquidBlock.java ++++ b/net/minecraft/world/level/block/LiquidBlock.java +@@ -42,7 +42,7 @@ + public class LiquidBlock extends Block implements BucketPickup { + + private static final Codec FLOWING_FLUID = BuiltInRegistries.FLUID.byNameCodec().comapFlatMap((fluidtype) -> { +- DataResult dataresult; ++ DataResult dataresult; // CraftBukkit - decompile error + + if (fluidtype instanceof FlowingFluid fluidtypeflowing) { + dataresult = DataResult.success(fluidtypeflowing); +@@ -141,11 +141,31 @@ + @Override + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); ++ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + } + + } + ++ // Paper start - Configurable speed for water flowing over lava ++ public int getFlowSpeed(Level world, BlockPos blockposition) { ++ if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { ++ if ( ++ isLava(world, blockposition.north(1)) || ++ isLava(world, blockposition.south(1)) || ++ isLava(world, blockposition.west(1)) || ++ isLava(world, blockposition.east(1)) ++ ) { ++ return world.paperConfig().environment.waterOverLavaFlowSpeed; ++ } ++ } ++ return this.fluid.getTickDelay(world); ++ } ++ private static boolean isLava(Level world, BlockPos blockPos) { ++ final FluidState fluidState = world.getFluidIfLoaded(blockPos); ++ return fluidState != null && fluidState.is(FluidTags.LAVA); ++ } ++ // Paper end - Configurable speed for water flowing over lava ++ + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { + if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { +@@ -158,7 +178,7 @@ + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); ++ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + } + + } +@@ -175,14 +195,20 @@ + if (world.getFluidState(blockposition1).is(FluidTags.WATER)) { + Block block = world.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; + +- world.setBlockAndUpdate(pos, block.defaultBlockState()); +- this.fizz(world, pos); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, block.defaultBlockState())) { ++ this.fizz(world, pos); ++ } ++ // CraftBukkit end + return false; + } + + if (flag && world.getBlockState(blockposition1).is(Blocks.BLUE_ICE)) { +- world.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState()); +- this.fizz(world, pos); ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, Blocks.BASALT.defaultBlockState())) { ++ this.fizz(world, pos); ++ } ++ // CraftBukkit end + return false; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch new file mode 100644 index 0000000000..9fc3631803 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/LoomBlock.java ++++ b/net/minecraft/world/level/block/LoomBlock.java +@@ -33,8 +33,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_LOOM); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch new file mode 100644 index 0000000000..6febd2458e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/MagmaBlock.java ++++ b/net/minecraft/world/level/block/MagmaBlock.java +@@ -30,7 +30,7 @@ + @Override + public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { + if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) { +- entity.hurt(world.damageSources().hotFloor(), 1.0F); ++ entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // CraftBukkit + } + + super.stepOn(world, pos, state, entity); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch new file mode 100644 index 0000000000..8a2e3114dc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/MangrovePropaguleBlock.java ++++ b/net/minecraft/world/level/block/MangrovePropaguleBlock.java +@@ -123,7 +123,7 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!isHanging(state)) { +- if (random.nextInt(7) == 0) { ++ if (random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers + this.advanceTree(world, pos, state, random); + } + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch new file mode 100644 index 0000000000..31dd40a615 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch @@ -0,0 +1,43 @@ +--- a/net/minecraft/world/level/block/MultifaceSpreader.java ++++ b/net/minecraft/world/level/block/MultifaceSpreader.java +@@ -156,7 +156,7 @@ + world.getChunk(growPos.pos()).markPosForPostprocessing(growPos.pos()); + } + +- return world.setBlock(growPos.pos(), iblockdata1, 2); ++ return org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, growPos.source(), growPos.pos(), iblockdata1, 2); // CraftBukkit + } else { + return false; + } +@@ -174,19 +174,19 @@ + SAME_POSITION { + @Override + public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) { +- return new MultifaceSpreader.SpreadPos(pos, newDirection); ++ return new MultifaceSpreader.SpreadPos(pos, newDirection, pos); // CraftBukkit + } + }, + SAME_PLANE { + @Override + public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) { +- return new MultifaceSpreader.SpreadPos(pos.relative(newDirection), oldDirection); ++ return new MultifaceSpreader.SpreadPos(pos.relative(newDirection), oldDirection, pos); // CraftBukkit + } + }, + WRAP_AROUND { + @Override + public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) { +- return new MultifaceSpreader.SpreadPos(pos.relative(newDirection).relative(oldDirection), newDirection.getOpposite()); ++ return new MultifaceSpreader.SpreadPos(pos.relative(newDirection).relative(oldDirection), newDirection.getOpposite(), pos); // CraftBukkit + } + }; + +@@ -195,7 +195,7 @@ + public abstract MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection); + } + +- public static record SpreadPos(BlockPos pos, Direction face) { ++ public static record SpreadPos(BlockPos pos, Direction face, BlockPos source) { // CraftBukkit + + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch new file mode 100644 index 0000000000..7fa4b44f28 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -19,6 +19,9 @@ + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.TreeType; ++// CraftBukkit end + + public class MushroomBlock extends BushBlock implements BonemealableBlock { + +@@ -48,7 +51,7 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if (random.nextInt(25) == 0) { ++ if (random.nextFloat() < (world.spigotConfig.mushroomModifier / (100.0f * 25))) { // Spigot - SPIGOT-7159: Better modifier resolution + int i = 5; + boolean flag = true; + Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, -1, -4), pos.offset(4, 1, 4)).iterator(); +@@ -65,6 +68,7 @@ + } + + BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); ++ final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event + + for (int j = 0; j < 4; ++j) { + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +@@ -75,7 +79,7 @@ + } + + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +- world.setBlock(blockposition2, state, 2); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event + } + } + +@@ -101,6 +105,7 @@ + return false; + } else { + world.removeBlock(pos, false); ++ SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit + if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch new file mode 100644 index 0000000000..5b83537d76 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch @@ -0,0 +1,163 @@ +--- a/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -32,12 +32,19 @@ + import net.minecraft.world.level.block.state.properties.EnumProperty; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.dimension.DimensionType; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.portal.PortalShape; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.slf4j.Logger; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.entity.EntityPortalEnterEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++// CraftBukkit end + + public class NetherPortalBlock extends Block implements Portal { + +@@ -71,16 +78,21 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if (world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { ++ if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot + while (world.getBlockState(pos).is((Block) this)) { + pos = pos.below(); + } + + if (world.getBlockState(pos).isValidSpawn(world, pos, EntityType.ZOMBIFIED_PIGLIN)) { +- Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(world, pos.above(), EntitySpawnReason.STRUCTURE); ++ // CraftBukkit - set spawn reason to NETHER_PORTAL ++ Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(world, pos.above(), EntitySpawnReason.STRUCTURE, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL); + + if (entity != null) { + entity.setPortalCooldown(); ++ // Paper start - Add option to nerf pigmen from nether portals ++ entity.fromNetherPortal = true; ++ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; ++ // Paper end - Add option to nerf pigmen from nether portals + Entity entity1 = entity.getVehicle(); + + if (entity1 != null) { +@@ -103,7 +115,13 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { ++ // CraftBukkit start - Entity in portal ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) return; // Paper - make cancellable ++ // CraftBukkit end + entity.setAsInsidePortal(this, pos); + } + +@@ -121,51 +139,80 @@ + @Nullable + @Override + public TeleportTransition getPortalDestination(ServerLevel world, Entity entity, BlockPos pos) { +- ResourceKey resourcekey = world.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER; ++ // CraftBukkit start ++ ResourceKey resourcekey = world.getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER; + ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); ++ // Paper start - Add EntityPortalReadyEvent ++ io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); ++ if (!portalReadyEvent.callEvent()) { ++ entity.portalProcess = null; ++ return null; ++ } ++ worldserver1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle(); ++ // Paper end - Add EntityPortalReadyEvent + + if (worldserver1 == null) { +- return null; ++ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist + } else { +- boolean flag = worldserver1.dimension() == Level.NETHER; ++ boolean flag = worldserver1.getTypeKey() == LevelStem.NETHER; ++ // CraftBukkit end + WorldBorder worldborder = worldserver1.getWorldBorder(); + double d0 = DimensionType.getTeleportationScale(world.dimensionType(), worldserver1.dimensionType()); + BlockPos blockposition1 = worldborder.clampToBounds(entity.getX() * d0, entity.getY(), entity.getZ() * d0); ++ // Paper start - Configurable portal search radius ++ int portalSearchRadius = worldserver1.paperConfig().environment.portalSearchRadius; ++ if (entity.level().paperConfig().environment.portalSearchVanillaDimensionScaling && flag) { // flag = is going to nether ++ portalSearchRadius = (int) (portalSearchRadius / worldserver1.dimensionType().coordinateScale()); ++ } ++ // Paper end - Configurable portal search radius ++ // CraftBukkit start ++ CraftPortalEvent event = entity.callPortalEvent(entity, CraftLocation.toBukkit(blockposition1, worldserver1.getWorld()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, worldserver1.paperConfig().environment.portalCreateRadius); // Paper - use custom portal search radius ++ if (event == null) { ++ return null; ++ } ++ worldserver1 = ((CraftWorld) event.getTo().getWorld()).getHandle(); ++ worldborder = worldserver1.getWorldBorder(); ++ blockposition1 = worldborder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()); + +- return this.getExitPortal(worldserver1, entity, pos, blockposition1, flag, worldborder); ++ return this.getExitPortal(worldserver1, entity, pos, blockposition1, flag, worldborder, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius()); + } + } + + @Nullable +- private TeleportTransition getExitPortal(ServerLevel world, Entity entity, BlockPos pos, BlockPos scaledPos, boolean inNether, WorldBorder worldBorder) { +- Optional optional = world.getPortalForcer().findClosestPortalPosition(scaledPos, inNether, worldBorder); ++ private TeleportTransition getExitPortal(ServerLevel worldserver, Entity entity, BlockPos blockposition, BlockPos blockposition1, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) { ++ Optional optional = worldserver.getPortalForcer().findClosestPortalPosition(blockposition1, worldborder, searchRadius); + BlockUtil.FoundRectangle blockutil_rectangle; + TeleportTransition.PostTeleportTransition teleporttransition_a; + + if (optional.isPresent()) { + BlockPos blockposition2 = (BlockPos) optional.get(); +- BlockState iblockdata = world.getBlockState(blockposition2); ++ BlockState iblockdata = worldserver.getBlockState(blockposition2); + + blockutil_rectangle = BlockUtil.getLargestRectangleAround(blockposition2, (Direction.Axis) iblockdata.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, (blockposition3) -> { +- return world.getBlockState(blockposition3) == iblockdata; ++ return worldserver.getBlockState(blockposition3) == iblockdata; + }); + teleporttransition_a = TeleportTransition.PLAY_PORTAL_SOUND.then((entity1) -> { + entity1.placePortalTicket(blockposition2); + }); +- } else { +- Direction.Axis enumdirection_enumaxis = (Direction.Axis) entity.level().getBlockState(pos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X); +- Optional optional1 = world.getPortalForcer().createPortal(scaledPos, enumdirection_enumaxis); ++ } else if (canCreatePortal) { ++ Direction.Axis enumdirection_enumaxis = (Direction.Axis) entity.level().getBlockState(blockposition).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X); ++ Optional optional1 = worldserver.getPortalForcer().createPortal(blockposition1, enumdirection_enumaxis, entity, createRadius); ++ // CraftBukkit end + + if (optional1.isEmpty()) { +- NetherPortalBlock.LOGGER.error("Unable to create a portal, likely target out of worldborder"); ++ // BlockPortal.LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit + return null; + } + + blockutil_rectangle = (BlockUtil.FoundRectangle) optional1.get(); + teleporttransition_a = TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET); ++ // CraftBukkit start ++ } else { ++ return null; ++ // CraftBukkit end + } + +- return NetherPortalBlock.getDimensionTransitionFromExit(entity, pos, blockutil_rectangle, world, teleporttransition_a); ++ return NetherPortalBlock.getDimensionTransitionFromExit(entity, blockposition, blockutil_rectangle, worldserver, teleporttransition_a); + } + + private static TeleportTransition getDimensionTransitionFromExit(Entity entity, BlockPos pos, BlockUtil.FoundRectangle exitPortalRectangle, ServerLevel world, TeleportTransition.PostTeleportTransition postDimensionTransition) { +@@ -203,7 +250,7 @@ + Vec3 vec3d1 = new Vec3((double) blockposition.getX() + (flag ? d2 : d4), (double) blockposition.getY() + d3, (double) blockposition.getZ() + (flag ? d4 : d2)); + Vec3 vec3d2 = PortalShape.findCollisionFreePosition(vec3d1, world, entity, entitysize); + +- return new TeleportTransition(world, vec3d2, Vec3.ZERO, (float) i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postDimensionTransition); ++ return new TeleportTransition(world, vec3d2, Vec3.ZERO, (float) i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postDimensionTransition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch new file mode 100644 index 0000000000..849dc04024 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/NetherWartBlock.java ++++ b/net/minecraft/world/level/block/NetherWartBlock.java +@@ -52,9 +52,9 @@ + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + int i = (Integer) state.getValue(NetherWartBlock.AGE); + +- if (i < 3 && random.nextInt(10) == 0) { ++ if (i < 3 && random.nextFloat() < (world.spigotConfig.wartModifier / (100.0f * 10))) { // Spigot - SPIGOT-7159: Better modifier resolution + state = (BlockState) state.setValue(NetherWartBlock.AGE, i + 1); +- world.setBlock(pos, state, 2); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch new file mode 100644 index 0000000000..b1d5c77400 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch @@ -0,0 +1,78 @@ +--- a/net/minecraft/world/level/block/NoteBlock.java ++++ b/net/minecraft/world/level/block/NoteBlock.java +@@ -68,11 +68,13 @@ + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return this.defaultBlockState(); // Paper - place without considering instrument + return this.setInstrument(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); + } + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return state; // Paper - prevent noteblock instrument from updating + boolean flag = direction.getAxis() == Direction.Axis.Y; + + return flag ? this.setInstrument(world, pos, state) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -80,11 +82,13 @@ + + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return; // Paper - prevent noteblock powered-state from updating + boolean flag1 = world.hasNeighborSignal(pos); + + if (flag1 != (Boolean) state.getValue(NoteBlock.POWERED)) { + if (flag1) { + this.playNote((Entity) null, state, world, pos); ++ state = world.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event + } + + world.setBlock(pos, (BlockState) state.setValue(NoteBlock.POWERED, flag1), 3); +@@ -94,6 +98,13 @@ + + private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { + if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { ++ // CraftBukkit start ++ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); ++ // if (event.isCancelled()) { ++ // return; ++ // } ++ // CraftBukkit end ++ // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event? + world.blockEvent(pos, this, 0, 0); + world.gameEvent(entity, (Holder) GameEvent.NOTE_BLOCK_PLAY, pos); + } +@@ -108,7 +119,7 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- state = (BlockState) state.cycle(NoteBlock.NOTE); ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) state = (BlockState) state.cycle(NoteBlock.NOTE); // Paper - prevent noteblock note from updating + world.setBlock(pos, state, 3); + this.playNote(player, state, world, pos); + player.awardStat(Stats.TUNE_NOTEBLOCK); +@@ -132,10 +143,14 @@ + @Override + protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + NoteBlockInstrument blockpropertyinstrument = (NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT); ++ // Paper start - move NotePlayEvent call to fix instrument/note changes ++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, blockpropertyinstrument, state.getValue(NOTE)); ++ if (event.isCancelled()) return false; ++ // Paper end - move NotePlayEvent call to fix instrument/note changes + float f; + + if (blockpropertyinstrument.isTunable()) { +- int k = (Integer) state.getValue(NoteBlock.NOTE); ++ int k = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes + + f = NoteBlock.getPitchFromNote(k); + world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); +@@ -154,7 +169,7 @@ + + holder = Holder.direct(SoundEvent.createVariableRangeEvent(minecraftkey)); + } else { +- holder = blockpropertyinstrument.getSoundEvent(); ++ holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes + } + + world.playSeededSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, holder, SoundSource.RECORDS, 3.0F, f, world.random.nextLong()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch new file mode 100644 index 0000000000..cc4aae4d48 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/NyliumBlock.java ++++ b/net/minecraft/world/level/block/NyliumBlock.java +@@ -41,6 +41,11 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!NyliumBlock.canBeNylium(state, world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.NETHERRACK.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlockAndUpdate(pos, Blocks.NETHERRACK.defaultBlockState()); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch new file mode 100644 index 0000000000..0718f3ab29 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/level/block/ObserverBlock.java ++++ b/net/minecraft/world/level/block/ObserverBlock.java +@@ -18,6 +18,8 @@ + import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; + import net.minecraft.world.level.redstone.Orientation; + ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++ + public class ObserverBlock extends DirectionalBlock { + + public static final MapCodec CODEC = simpleCodec(ObserverBlock::new); +@@ -51,8 +53,18 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if ((Boolean) state.getValue(ObserverBlock.POWERED)) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(ObserverBlock.POWERED, false), 2); + } else { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(ObserverBlock.POWERED, true), 2); + world.scheduleTick(pos, (Block) this, 2); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch new file mode 100644 index 0000000000..bdd3b319d7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -107,6 +107,7 @@ + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + serverLevel.destroyBlock(pos, true, entity); + } +@@ -131,7 +132,7 @@ + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + float f = CropBlock.getGrowthSpeed(this, world, pos); +- boolean bl = random.nextInt((int)(25.0F / f) + 1) == 0; ++ boolean bl = random.nextFloat() < (world.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / f) + 1))); // Paper - Fix Spigot growth modifiers + if (bl) { + this.grow(world, state, pos, 1); + } +@@ -141,7 +142,7 @@ + int i = Math.min(state.getValue(AGE) + amount, 4); + if (this.canGrow(world, pos, state, i)) { + BlockState blockState = state.setValue(AGE, Integer.valueOf(i)); +- world.setBlock(pos, blockState, 2); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, blockState, 2)) return; // Paper + if (isDouble(i)) { + world.setBlock(pos.above(), blockState.setValue(HALF, DoubleBlockHalf.UPPER), 3); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch new file mode 100644 index 0000000000..3536598553 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -136,6 +136,11 @@ + ServerLevel worldserver = (ServerLevel) world; + + if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) { ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ return; ++ } ++ // CraftBukkit end + world.destroyBlock(blockposition, true); + } + } +@@ -146,7 +151,7 @@ + @Override + public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { + if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) { +- entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite()); ++ entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite().directBlock(world, pos)); // CraftBukkit + } else { + super.fallOn(world, state, pos, entity, fallDistance); + } +@@ -214,10 +219,13 @@ + if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) { + BlockState iblockdata1 = Blocks.CLAY.defaultBlockState(); + +- world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1); ++ // Paper start - Call BlockFormEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) { + Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1)); + world.levelEvent(1504, blockposition1, 0); ++ } ++ // Paper end - Call BlockFormEvent + } else { + BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype); + +@@ -391,15 +399,15 @@ + if (PointedDripstoneBlock.isUnmergedTipWithDirection(iblockdata, direction.getOpposite())) { + PointedDripstoneBlock.createMergedTips(iblockdata, world, blockposition1); + } else if (iblockdata.isAir() || iblockdata.is(Blocks.WATER)) { +- PointedDripstoneBlock.createDripstone(world, blockposition1, direction, DripstoneThickness.TIP); ++ PointedDripstoneBlock.createDripstone(world, blockposition1, direction, DripstoneThickness.TIP, pos); // CraftBukkit + } + + } + +- private static void createDripstone(LevelAccessor world, BlockPos pos, Direction direction, DripstoneThickness thickness) { +- BlockState iblockdata = (BlockState) ((BlockState) ((BlockState) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, direction)).setValue(PointedDripstoneBlock.THICKNESS, thickness)).setValue(PointedDripstoneBlock.WATERLOGGED, world.getFluidState(pos).getType() == Fluids.WATER); ++ private static void createDripstone(LevelAccessor generatoraccess, BlockPos blockposition, Direction enumdirection, DripstoneThickness dripstonethickness, BlockPos source) { // CraftBukkit ++ BlockState iblockdata = (BlockState) ((BlockState) ((BlockState) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, enumdirection)).setValue(PointedDripstoneBlock.THICKNESS, dripstonethickness)).setValue(PointedDripstoneBlock.WATERLOGGED, generatoraccess.getFluidState(blockposition).getType() == Fluids.WATER); + +- world.setBlock(pos, iblockdata, 3); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, source, blockposition, iblockdata, 3); // CraftBukkit + } + + private static void createMergedTips(BlockState state, LevelAccessor world, BlockPos pos) { +@@ -414,8 +422,8 @@ + blockposition1 = pos.below(); + } + +- PointedDripstoneBlock.createDripstone(world, blockposition2, Direction.DOWN, DripstoneThickness.TIP_MERGE); +- PointedDripstoneBlock.createDripstone(world, blockposition1, Direction.UP, DripstoneThickness.TIP_MERGE); ++ PointedDripstoneBlock.createDripstone(world, blockposition2, Direction.DOWN, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit ++ PointedDripstoneBlock.createDripstone(world, blockposition1, Direction.UP, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit + } + + public static void spawnDripParticle(Level world, BlockPos pos, BlockState state) { +@@ -448,7 +456,7 @@ + + return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, enumdirection.getAxisDirection(), bipredicate, (iblockdata1) -> { + return PointedDripstoneBlock.isTip(iblockdata1, allowMerged); +- }, range).orElse((Object) null); ++ }, range).orElse(null); // CraftBukkit - decompile error + } + } + +@@ -564,7 +572,7 @@ + return PointedDripstoneBlock.canDripThrough(world, blockposition1, iblockdata); + }; + +- return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.DOWN.getAxisDirection(), bipredicate, predicate, 11).orElse((Object) null); ++ return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.DOWN.getAxisDirection(), bipredicate, predicate, 11).orElse(null); // CraftBukkit - decompile error + } + + @Nullable +@@ -573,7 +581,7 @@ + return PointedDripstoneBlock.canDripThrough(world, blockposition1, iblockdata); + }; + +- return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.UP.getAxisDirection(), bipredicate, PointedDripstoneBlock::canDrip, 11).orElse((Object) null); ++ return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.UP.getAxisDirection(), bipredicate, PointedDripstoneBlock::canDrip, 11).orElse(null); // CraftBukkit - decompile error + } + + public static Fluid getCauldronFillFluidType(ServerLevel world, BlockPos pos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch new file mode 100644 index 0000000000..0f012c2b02 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/level/block/PowderSnowBlock.java ++++ b/net/minecraft/world/level/block/PowderSnowBlock.java +@@ -59,6 +59,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!(entity instanceof LivingEntity) || entity.getInBlockState().is((Block) this)) { + entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); + if (world.isClientSide) { +@@ -73,7 +74,12 @@ + + entity.setIsInPowderSnow(true); + if (world instanceof ServerLevel worldserver) { +- if (entity.isOnFire() && (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player) && entity.mayInteract(worldserver, pos)) { ++ // CraftBukkit start ++ if (entity.isOnFire() && entity.mayInteract(worldserver, pos)) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { ++ return; ++ } ++ // CraftBukkit end + world.destroyBlock(pos, false); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch new file mode 100644 index 0000000000..e0443a21f6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch @@ -0,0 +1,24 @@ +--- a/net/minecraft/world/level/block/PoweredRailBlock.java ++++ b/net/minecraft/world/level/block/PoweredRailBlock.java +@@ -11,6 +11,7 @@ + import net.minecraft.world.level.block.state.properties.EnumProperty; + import net.minecraft.world.level.block.state.properties.Property; + import net.minecraft.world.level.block.state.properties.RailShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class PoweredRailBlock extends BaseRailBlock { + +@@ -120,6 +121,13 @@ + boolean flag1 = world.hasNeighborSignal(pos) || this.findPoweredRailSignal(world, pos, state, true, 0) || this.findPoweredRailSignal(world, pos, state, false, 0); + + if (flag1 != flag) { ++ // CraftBukkit start ++ int power = flag ? 15 : 0; ++ int newPower = CraftEventFactory.callRedstoneChange(world, pos, power, 15 - power).getNewCurrent(); ++ if (newPower == power) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(PoweredRailBlock.POWERED, flag1), 3); + world.updateNeighborsAt(pos.below(), this); + if (((RailShape) state.getValue(PoweredRailBlock.SHAPE)).isSlope()) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch new file mode 100644 index 0000000000..4b6ba046eb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/world/level/block/PressurePlateBlock.java ++++ b/net/minecraft/world/level/block/PressurePlateBlock.java +@@ -5,6 +5,7 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; +@@ -12,6 +13,8 @@ + import net.minecraft.world.level.block.state.properties.BlockSetType; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.BooleanProperty; ++import org.bukkit.event.entity.EntityInteractEvent; ++// CraftBukkit end + + public class PressurePlateBlock extends BasePressurePlateBlock { + +@@ -44,7 +47,7 @@ + + @Override + protected int getSignalStrength(Level world, BlockPos pos) { +- Class oclass; ++ Class oclass; // CraftBukkit + + switch (this.type.pressurePlateSensitivity()) { + case EVERYTHING: +@@ -59,7 +62,31 @@ + + Class oclass1 = oclass; + +- return getEntityCount(world, PressurePlateBlock.TOUCH_AABB.move(pos), oclass1) > 0 ? 15 : 0; ++ // CraftBukkit start - Call interact event when turning on a pressure plate ++ for (Entity entity : getEntities(world, PressurePlateBlock.TOUCH_AABB.move(pos), oclass)) { ++ if (this.getSignalForState(world.getBlockState(pos)) == 0) { ++ org.bukkit.World bworld = world.getWorld(); ++ org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager(); ++ org.bukkit.event.Cancellable cancellable; ++ ++ if (entity instanceof Player) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) 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; ++ } ++ } ++ ++ return 15; ++ } ++ ++ return 0; ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch new file mode 100644 index 0000000000..d3d384ea46 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/level/block/PumpkinBlock.java ++++ b/net/minecraft/world/level/block/PumpkinBlock.java +@@ -38,16 +38,24 @@ + } else if (world.isClientSide) { + return InteractionResult.SUCCESS; + } else { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end - Add PlayerShearBlockEvent + Direction direction = hit.getDirection(); + Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction; + world.playSound(null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11); ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { // Paper - Add PlayerShearBlockEvent + ItemEntity itemEntity = new ItemEntity( + world, + (double)pos.getX() + 0.5 + (double)direction2.getStepX() * 0.65, + (double)pos.getY() + 0.1, + (double)pos.getZ() + 0.5 + (double)direction2.getStepZ() * 0.65, +- new ItemStack(Items.PUMPKIN_SEEDS, 4) ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item) // Paper - Add PlayerShearBlockEvent + ); + itemEntity.setDeltaMovement( + 0.05 * (double)direction2.getStepX() + world.random.nextDouble() * 0.02, +@@ -55,6 +63,7 @@ + 0.05 * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02 + ); + world.addFreshEntity(itemEntity); ++ } // Paper - Add PlayerShearBlockEvent + stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); + world.gameEvent(player, GameEvent.SHEAR, pos); + player.awardStat(Stats.ITEM_USED.get(Items.SHEARS)); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch new file mode 100644 index 0000000000..b79bfb5efd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/level/block/RailState.java ++++ b/net/minecraft/world/level/block/RailState.java +@@ -17,6 +17,12 @@ + private final boolean isStraight; + private final List connections = Lists.newArrayList(); + ++ // Paper start - Fix some rails connecting improperly ++ public boolean isValid() { ++ return this.level.getBlockState(this.pos).getBlock() == this.state.getBlock(); ++ } ++ // Paper end - Fix some rails connecting improperly ++ + public RailState(Level world, BlockPos pos, BlockState state) { + this.level = world; + this.pos = pos; +@@ -141,6 +147,11 @@ + } + + private void connectTo(RailState placementHelper) { ++ // Paper start - Fix some rails connecting improperly ++ if (!this.isValid() || !placementHelper.isValid()) { ++ return; ++ } ++ // Paper end - Fix some rails connecting improperly + this.connections.add(placementHelper.pos); + BlockPos blockPos = this.pos.north(); + BlockPos blockPos2 = this.pos.south(); +@@ -331,10 +342,15 @@ + this.state = this.state.setValue(this.block.getShapeProperty(), railShape2); + if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { + this.level.setBlock(this.pos, this.state, 3); ++ // Paper start - Fix some rails connecting improperly ++ if (!this.isValid()) { ++ return this; ++ } ++ // Paper end - Fix some rails connecting improperly + + for (int i = 0; i < this.connections.size(); i++) { + RailState railState = this.getRail(this.connections.get(i)); +- if (railState != null) { ++ if (railState != null && railState.isValid()) { // Paper - Fix some rails connecting improperly + railState.removeSoftConnections(); + if (railState.canConnectTo(this)) { + railState.connectTo(this); +@@ -347,6 +363,6 @@ + } + + public BlockState getState() { +- return this.state; ++ return this.level.getBlockState(this.pos); // Paper - Fix some rails connecting improperly + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch new file mode 100644 index 0000000000..29e5229732 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch @@ -0,0 +1,102 @@ +--- a/net/minecraft/world/level/block/RedStoneOreBlock.java ++++ b/net/minecraft/world/level/block/RedStoneOreBlock.java +@@ -20,6 +20,10 @@ + import net.minecraft.world.level.block.state.StateDefinition; + import net.minecraft.world.level.block.state.properties.BooleanProperty; + import net.minecraft.world.phys.BlockHitResult; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityInteractEvent; ++// CraftBukkit end + + public class RedStoneOreBlock extends Block { + +@@ -38,14 +42,27 @@ + + @Override + protected void attack(BlockState state, Level world, BlockPos pos, Player player) { +- RedStoneOreBlock.interact(state, world, pos); ++ RedStoneOreBlock.interact(state, world, pos, player); // CraftBukkit - add entityhuman + super.attack(state, world, pos, player); + } + + @Override + public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { + if (!entity.isSteppingCarefully()) { +- RedStoneOreBlock.interact(state, world, pos); ++ // CraftBukkit start ++ if (entity instanceof Player) { ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ if (!event.isCancelled()) { ++ RedStoneOreBlock.interact(world.getBlockState(pos), world, pos, entity); // add entity ++ } ++ } else { ++ EntityInteractEvent event = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ RedStoneOreBlock.interact(world.getBlockState(pos), world, pos, entity); // add entity ++ } ++ } ++ // CraftBukkit end + } + + super.stepOn(world, pos, state, entity); +@@ -56,16 +73,21 @@ + if (world.isClientSide) { + RedStoneOreBlock.spawnParticles(world, pos); + } else { +- RedStoneOreBlock.interact(state, world, pos); ++ RedStoneOreBlock.interact(state, world, pos, player); // CraftBukkit - add entityhuman + } + + return (InteractionResult) (stack.getItem() instanceof BlockItem && (new BlockPlaceContext(player, hand, stack, hit)).canPlace() ? InteractionResult.PASS : InteractionResult.SUCCESS); + } + +- private static void interact(BlockState state, Level world, BlockPos pos) { +- RedStoneOreBlock.spawnParticles(world, pos); +- if (!(Boolean) state.getValue(RedStoneOreBlock.LIT)) { +- world.setBlock(pos, (BlockState) state.setValue(RedStoneOreBlock.LIT, true), 3); ++ private static void interact(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) { // CraftBukkit - add Entity ++ RedStoneOreBlock.spawnParticles(world, blockposition); ++ if (!(Boolean) iblockdata.getValue(RedStoneOreBlock.LIT)) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(RedStoneOreBlock.LIT, true))) { ++ return; ++ } ++ // CraftBukkit end ++ world.setBlock(blockposition, (BlockState) iblockdata.setValue(RedStoneOreBlock.LIT, true), 3); + } + + } +@@ -78,6 +100,11 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if ((Boolean) state.getValue(RedStoneOreBlock.LIT)) { ++ // CraftBukkit start ++ if (CraftEventFactory.callBlockFadeEvent(world, pos, state.setValue(RedStoneOreBlock.LIT, false)).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(RedStoneOreBlock.LIT, false), 3); + } + +@@ -86,10 +113,17 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- this.tryDropExperience(world, pos, tool, UniformInt.of(1, 5)); ++ // CraftBukkit start - Delegated to getExpDrop ++ } ++ ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ return this.tryDropExperience(worldserver, blockposition, itemstack, UniformInt.of(1, 5)); + } + ++ return 0; ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch new file mode 100644 index 0000000000..dc1b67c34c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch @@ -0,0 +1,35 @@ +--- a/net/minecraft/world/level/block/RedstoneLampBlock.java ++++ b/net/minecraft/world/level/block/RedstoneLampBlock.java +@@ -13,6 +13,8 @@ + import net.minecraft.world.level.block.state.properties.BooleanProperty; + import net.minecraft.world.level.redstone.Orientation; + ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++ + public class RedstoneLampBlock extends Block { + + public static final MapCodec CODEC = simpleCodec(RedstoneLampBlock::new); +@@ -43,6 +45,11 @@ + if (flag1) { + world.scheduleTick(pos, (Block) this, 4); + } else { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.cycle(RedstoneLampBlock.LIT), 2); + } + } +@@ -53,6 +60,11 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if ((Boolean) state.getValue(RedstoneLampBlock.LIT) && !world.hasNeighborSignal(pos)) { ++ // CraftBukkit start ++ if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) { ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.cycle(RedstoneLampBlock.LIT), 2); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch new file mode 100644 index 0000000000..69d1dcb6e7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch @@ -0,0 +1,88 @@ +--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -22,11 +22,13 @@ + import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; + import net.minecraft.world.level.redstone.Orientation; + ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++ + public class RedstoneTorchBlock extends BaseTorchBlock { + + public static final MapCodec CODEC = simpleCodec(RedstoneTorchBlock::new); + public static final BooleanProperty LIT = BlockStateProperties.LIT; +- private static final Map> RECENT_TOGGLES = new WeakHashMap(); ++ // Paper - Faster redstone torch rapid clock removal; Move the mapped list to World + public static final int RECENT_TOGGLE_TIMER = 60; + public static final int MAX_RECENT_TOGGLES = 8; + public static final int RESTART_DELAY = 160; +@@ -79,14 +81,34 @@ + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + boolean flag = this.hasNeighborSignal(world, pos, state); +- List list = (List) RedstoneTorchBlock.RECENT_TOGGLES.get(world); +- +- while (list != null && !list.isEmpty() && world.getGameTime() - ((RedstoneTorchBlock.Toggle) list.get(0)).when > 60L) { +- list.remove(0); ++ // Paper start - Faster redstone torch rapid clock removal ++ java.util.ArrayDeque redstoneUpdateInfos = world.redstoneUpdateInfos; ++ if (redstoneUpdateInfos != null) { ++ RedstoneTorchBlock.Toggle curr; ++ while ((curr = redstoneUpdateInfos.peek()) != null && world.getGameTime() - curr.when > 60L) { ++ redstoneUpdateInfos.poll(); ++ } + } ++ // Paper end - Faster redstone torch rapid clock removal + ++ // CraftBukkit start ++ org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager(); ++ org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ int oldCurrent = ((Boolean) state.getValue(RedstoneTorchBlock.LIT)).booleanValue() ? 15 : 0; ++ ++ BlockRedstoneEvent event = new BlockRedstoneEvent(block, oldCurrent, oldCurrent); ++ // CraftBukkit end + if ((Boolean) state.getValue(RedstoneTorchBlock.LIT)) { + if (flag) { ++ // CraftBukkit start ++ if (oldCurrent != 0) { ++ event.setNewCurrent(0); ++ manager.callEvent(event); ++ if (event.getNewCurrent() != 0) { ++ return; ++ } ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(RedstoneTorchBlock.LIT, false), 3); + if (RedstoneTorchBlock.isToggledTooFrequently(world, pos, true)) { + world.levelEvent(1502, pos, 0); +@@ -94,6 +116,15 @@ + } + } + } else if (!flag && !RedstoneTorchBlock.isToggledTooFrequently(world, pos, false)) { ++ // CraftBukkit start ++ if (oldCurrent != 15) { ++ event.setNewCurrent(15); ++ manager.callEvent(event); ++ if (event.getNewCurrent() != 15) { ++ return; ++ } ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) state.setValue(RedstoneTorchBlock.LIT, true), 3); + } + +@@ -134,9 +165,12 @@ + } + + private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) { +- List list = (List) RedstoneTorchBlock.RECENT_TOGGLES.computeIfAbsent(world, (iblockaccess) -> { +- return Lists.newArrayList(); +- }); ++ // Paper start - Faster redstone torch rapid clock removal ++ java.util.ArrayDeque list = world.redstoneUpdateInfos; ++ if (list == null) { ++ list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>(); ++ } ++ // Paper end - Faster redstone torch rapid clock removal + + if (addNew) { + list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), world.getGameTime())); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch new file mode 100644 index 0000000000..86e3e80aca --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch @@ -0,0 +1,46 @@ +--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -88,9 +88,14 @@ + ServerPlayer entityplayer = (ServerPlayer) player; + + if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) { +- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true); ++ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent + world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.SUCCESS_SERVER; ++ // Paper start - Add PlayerSetSpawnEvent ++ } else { ++ return InteractionResult.FAIL; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } + } + +@@ -127,15 +132,16 @@ + } + + private void explode(BlockState state, Level world, final BlockPos explodedPos) { ++ org.bukkit.block.BlockState blockState = org.bukkit.craftbukkit.block.CraftBlock.at(world, explodedPos).getState(); // CraftBukkit - capture BlockState before remove block + world.removeBlock(explodedPos, false); +- Stream stream = Direction.Plane.HORIZONTAL.stream(); ++ Stream stream = Direction.Plane.HORIZONTAL.stream(); // CraftBukkit - decompile error + + Objects.requireNonNull(explodedPos); + boolean flag = stream.map(explodedPos::relative).anyMatch((blockposition1) -> { + return RespawnAnchorBlock.isWaterThatWouldFlow(blockposition1, world); + }); + final boolean flag1 = flag || world.getFluidState(explodedPos.above()).is(FluidTags.WATER); +- ExplosionDamageCalculator explosiondamagecalculator = new ExplosionDamageCalculator(this) { ++ ExplosionDamageCalculator explosiondamagecalculator = new ExplosionDamageCalculator() { // CraftBukkit - decompile error + @Override + public Optional getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState) { + return pos.equals(explodedPos) && flag1 ? Optional.of(Blocks.WATER.getExplosionResistance()) : super.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState); +@@ -143,7 +149,7 @@ + }; + Vec3 vec3d = explodedPos.getCenter(); + +- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); ++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state + } + + public static boolean canSetSpawn(Level world) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch new file mode 100644 index 0000000000..de8d7301e4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/RootedDirtBlock.java ++++ b/net/minecraft/world/level/block/RootedDirtBlock.java +@@ -34,7 +34,7 @@ + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { +- world.setBlockAndUpdate(pos.below(), Blocks.HANGING_ROOTS.defaultBlockState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState()); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch new file mode 100644 index 0000000000..dbd9b130a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch @@ -0,0 +1,117 @@ +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -10,12 +10,19 @@ + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.block.grower.TreeGrower; + import net.minecraft.world.level.block.state.BlockBehaviour; +-import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.StateDefinition; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.TreeType; ++import org.bukkit.block.BlockState; ++import org.bukkit.craftbukkit.block.CapturedBlockState; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.world.StructureGrowEvent; ++// CraftBukkit end + + public class SaplingBlock extends BushBlock implements BonemealableBlock { + +@@ -28,6 +35,7 @@ + protected static final float AABB_OFFSET = 6.0F; + protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D); + protected final TreeGrower treeGrower; ++ public static TreeType treeType; // CraftBukkit + + @Override + public MapCodec codec() { +@@ -37,48 +45,74 @@ + protected SaplingBlock(TreeGrower generator, BlockBehaviour.Properties settings) { + super(settings); + this.treeGrower = generator; +- this.registerDefaultState((BlockState) ((BlockState) this.stateDefinition.any()).setValue(SaplingBlock.STAGE, 0)); ++ this.registerDefaultState((net.minecraft.world.level.block.state.BlockState) ((net.minecraft.world.level.block.state.BlockState) this.stateDefinition.any()).setValue(SaplingBlock.STAGE, 0)); + } + + @Override +- protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { ++ protected VoxelShape getShape(net.minecraft.world.level.block.state.BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + return SaplingBlock.SHAPE; + } + + @Override +- protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if (world.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextInt(7) == 0) { ++ protected void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (world.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0f * 7))) { // Spigot - SPIGOT-7159: Better modifier resolution + this.advanceTree(world, pos, state, random); + } + + } + +- public void advanceTree(ServerLevel world, BlockPos pos, BlockState state, RandomSource random) { ++ public void advanceTree(ServerLevel world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, RandomSource random) { + if ((Integer) state.getValue(SaplingBlock.STAGE) == 0) { +- world.setBlock(pos, (BlockState) state.cycle(SaplingBlock.STAGE), 4); ++ world.setBlock(pos, (net.minecraft.world.level.block.state.BlockState) state.cycle(SaplingBlock.STAGE), 4); + } else { +- this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); ++ // CraftBukkit start ++ if (world.captureTreeGeneration) { ++ this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); ++ } else { ++ world.captureTreeGeneration = true; ++ this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); ++ world.captureTreeGeneration = false; ++ if (world.capturedBlockStates.size() > 0) { ++ TreeType treeType = SaplingBlock.treeType; ++ SaplingBlock.treeType = null; ++ Location location = CraftLocation.toBukkit(pos, world.getWorld()); ++ java.util.List blocks = new java.util.ArrayList<>(world.capturedBlockStates.values()); ++ world.capturedBlockStates.clear(); ++ StructureGrowEvent event = null; ++ if (treeType != null) { ++ event = new StructureGrowEvent(location, treeType, false, null, blocks); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event == null || !event.isCancelled()) { ++ for (BlockState blockstate : blocks) { ++ CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed ++ } ++ } ++ } ++ } ++ // CraftBukkit end + } + + } + + @Override +- public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state) { ++ public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { + return true; + } + + @Override +- public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) { ++ public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { + return (double) world.random.nextFloat() < 0.45D; + } + + @Override +- public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { + this.advanceTree(world, pos, state, random); + } + + @Override +- protected void createBlockStateDefinition(StateDefinition.Builder builder) { ++ protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(SaplingBlock.STAGE); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch new file mode 100644 index 0000000000..d5960ee388 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/ScaffoldingBlock.java ++++ b/net/minecraft/world/level/block/ScaffoldingBlock.java +@@ -103,7 +103,7 @@ + int i = ScaffoldingBlock.getDistance(world, pos); + BlockState iblockdata1 = (BlockState) ((BlockState) state.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(world, pos, i)); + +- if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7) { ++ if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, iblockdata1.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state + if ((Integer) state.getValue(ScaffoldingBlock.DISTANCE) == 7) { + FallingBlockEntity.fall(world, pos, iblockdata1); + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch new file mode 100644 index 0000000000..b7e23a6303 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/SculkBlock.java ++++ b/net/minecraft/world/level/block/SculkBlock.java +@@ -43,8 +43,11 @@ + BlockPos blockposition2 = blockposition1.above(); + BlockState iblockdata = this.getRandomGrowthState(world, blockposition2, random, spreadManager.isWorldGeneration()); + +- world.setBlock(blockposition2, iblockdata, 3); +- world.playSound((Player) null, blockposition1, iblockdata.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); ++ // CraftBukkit start - Call BlockSpreadEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, catalystPos, blockposition2, iblockdata, 3)) { ++ world.playSound((Player) null, blockposition1, iblockdata.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F); ++ } ++ // CraftBukkit end + } + + return Math.max(0, i - j); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch new file mode 100644 index 0000000000..12314e3c0c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/block/SculkCatalystBlock.java ++++ b/net/minecraft/world/level/block/SculkCatalystBlock.java +@@ -63,9 +63,16 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- this.tryDropExperience(world, pos, tool, this.xpRange); ++ // CraftBukkit start - Delegate to getExpDrop ++ } ++ ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange); + } + ++ return 0; ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch new file mode 100644 index 0000000000..8e63b99d10 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch @@ -0,0 +1,88 @@ +--- a/net/minecraft/world/level/block/SculkSensorBlock.java ++++ b/net/minecraft/world/level/block/SculkSensorBlock.java +@@ -43,6 +43,10 @@ + import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++// CraftBukkit end + + public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterloggedBlock { + +@@ -104,6 +108,18 @@ + @Override + public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { + if (!world.isClientSide() && SculkSensorBlock.canActivate(state) && entity.getType() != EntityType.WARDEN) { ++ // CraftBukkit start ++ org.bukkit.event.Cancellable cancellable; ++ if (entity instanceof Player) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable); ++ } ++ if (cancellable.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + BlockEntity tileentity = world.getBlockEntity(pos); + + if (tileentity instanceof SculkSensorBlockEntity) { +@@ -198,10 +214,19 @@ + } + + public static boolean canActivate(BlockState state) { +- return SculkSensorBlock.getPhase(state) == SculkSensorPhase.INACTIVE; ++ return state.getBlock() instanceof SculkSensorBlock && SculkSensorBlock.getPhase(state) == SculkSensorPhase.INACTIVE; // Paper - Check for a valid type + } + + public static void deactivate(Level world, BlockPos pos, BlockState state) { ++ // CraftBukkit start ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), state.getValue(SculkSensorBlock.POWER), 0); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() > 0) { ++ world.setBlock(pos, state.setValue(SculkSensorBlock.POWER, eventRedstone.getNewCurrent()), 3); ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, (BlockState) ((BlockState) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.COOLDOWN)).setValue(SculkSensorBlock.POWER, 0), 3); + world.scheduleTick(pos, state.getBlock(), 10); + SculkSensorBlock.updateNeighbours(world, pos, state); +@@ -213,6 +238,15 @@ + } + + public void activate(@Nullable Entity sourceEntity, Level world, BlockPos pos, BlockState state, int power, int frequency) { ++ // CraftBukkit start ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), state.getValue(SculkSensorBlock.POWER), power); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() <= 0) { ++ return; ++ } ++ power = eventRedstone.getNewCurrent(); ++ // CraftBukkit end + world.setBlock(pos, (BlockState) ((BlockState) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.ACTIVE)).setValue(SculkSensorBlock.POWER, power), 3); + world.scheduleTick(pos, state.getBlock(), this.getActiveTicks()); + SculkSensorBlock.updateNeighbours(world, pos, state); +@@ -293,9 +327,16 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- this.tryDropExperience(world, pos, tool, ConstantInt.of(5)); ++ // CraftBukkit start - Delegate to getExpDrop ++ } ++ ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5)); + } + ++ return 0; ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch new file mode 100644 index 0000000000..8c254d2936 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/level/block/SculkShriekerBlock.java ++++ b/net/minecraft/world/level/block/SculkShriekerBlock.java +@@ -63,6 +63,7 @@ + ServerPlayer entityplayer = SculkShriekerBlockEntity.tryGetPlayer(entity); + + if (entityplayer != null) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(entityplayer, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null).isCancelled()) return; // CraftBukkit + worldserver.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent((sculkshriekerblockentity) -> { + sculkshriekerblockentity.tryShriek(worldserver, entityplayer); + }); +@@ -140,10 +141,17 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- this.tryDropExperience(world, pos, tool, ConstantInt.of(5)); ++ // CraftBukkit start - Delegate to getExpDrop ++ } ++ ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5)); + } + ++ return 0; ++ // CraftBukkit end + } + + @Nullable diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch new file mode 100644 index 0000000000..a334f182c5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch @@ -0,0 +1,98 @@ +--- a/net/minecraft/world/level/block/SculkSpreader.java ++++ b/net/minecraft/world/level/block/SculkSpreader.java +@@ -30,6 +30,7 @@ + import net.minecraft.core.Vec3i; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -37,9 +38,14 @@ + import net.minecraft.tags.TagKey; + import net.minecraft.util.RandomSource; + import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.SculkBloomEvent; ++// CraftBukkit end + + public class SculkSpreader { + +@@ -57,6 +63,7 @@ + private final int additionalDecayRate; + private List cursors = new ArrayList(); + private static final Logger LOGGER = LogUtils.getLogger(); ++ public Level level; // CraftBukkit + + public SculkSpreader(boolean worldGen, TagKey replaceableTag, int extraBlockChance, int maxDistance, int spreadChance, int decayChance) { + this.isWorldGeneration = worldGen; +@@ -111,7 +118,7 @@ + public void load(CompoundTag nbt) { + if (nbt.contains("cursors", 9)) { + this.cursors.clear(); +- DataResult dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().parse(new Dynamic(NbtOps.INSTANCE, nbt.getList("cursors", 10))); ++ DataResult> dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getList("cursors", 10))); // CraftBukkit - decompile error + Logger logger = SculkSpreader.LOGGER; + + Objects.requireNonNull(logger); +@@ -119,14 +126,14 @@ + int i = Math.min(list.size(), 32); + + for (int j = 0; j < i; ++j) { +- this.addCursor((SculkSpreader.ChargeCursor) list.get(j)); ++ this.addCursor((SculkSpreader.ChargeCursor) list.get(j), false); // Paper - don't fire event for block entity loading + } + } + + } + + public void save(CompoundTag nbt) { +- DataResult dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.cursors); ++ DataResult dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.cursors); // CraftBukkit - decompile error + Logger logger = SculkSpreader.LOGGER; + + Objects.requireNonNull(logger); +@@ -139,14 +146,27 @@ + while (charge > 0) { + int j = Math.min(charge, 1000); + +- this.addCursor(new SculkSpreader.ChargeCursor(pos, j)); ++ this.addCursor(new SculkSpreader.ChargeCursor(pos, j), true); // Paper - allow firing event for other causes + charge -= j; + } + + } + +- private void addCursor(SculkSpreader.ChargeCursor cursor) { ++ private void addCursor(SculkSpreader.ChargeCursor cursor, boolean fireEvent) { // Paper - add boolean to conditionally fire SculkBloomEvent + if (this.cursors.size() < 32) { ++ // CraftBukkit start ++ if (!this.isWorldGeneration() && fireEvent) { // CraftBukkit - SPIGOT-7475: Don't call event during world generation // Paper - add boolean to conditionally fire SculkBloomEvent ++ CraftBlock bukkitBlock = CraftBlock.at(this.level, cursor.pos); ++ SculkBloomEvent event = new SculkBloomEvent(bukkitBlock, cursor.getCharge()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ cursor.charge = event.getCharge(); ++ } ++ // CraftBukkit end ++ + this.cursors.add(cursor); + } + } +@@ -244,7 +264,7 @@ + this.charge = charge; + this.decayDelay = decay; + this.updateDelay = update; +- this.facings = (Set) faces.orElse((Object) null); ++ this.facings = (Set) faces.orElse(null); // CraftBukkit - decompile error + } + + public ChargeCursor(BlockPos pos, int charge) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch new file mode 100644 index 0000000000..4d009bd7c1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/world/level/block/SculkVeinBlock.java ++++ b/net/minecraft/world/level/block/SculkVeinBlock.java +@@ -101,28 +101,33 @@ + + @Override + public int attemptUseCharge(SculkSpreader.ChargeCursor cursor, LevelAccessor world, BlockPos catalystPos, RandomSource random, SculkSpreader spreadManager, boolean shouldConvertToBlock) { +- return shouldConvertToBlock && this.attemptPlaceSculk(spreadManager, world, cursor.getPos(), random) ? cursor.getCharge() - 1 : (random.nextInt(spreadManager.chargeDecayRate()) == 0 ? Mth.floor((float) cursor.getCharge() * 0.5F) : cursor.getCharge()); ++ // CraftBukkit - add source block ++ return shouldConvertToBlock && this.attemptPlaceSculk(spreadManager, world, cursor.getPos(), random, catalystPos) ? cursor.getCharge() - 1 : (random.nextInt(spreadManager.chargeDecayRate()) == 0 ? Mth.floor((float) cursor.getCharge() * 0.5F) : cursor.getCharge()); + } + +- private boolean attemptPlaceSculk(SculkSpreader spreadManager, LevelAccessor world, BlockPos pos, RandomSource random) { +- BlockState iblockdata = world.getBlockState(pos); +- TagKey tagkey = spreadManager.replaceableBlocks(); +- Iterator iterator = Direction.allShuffled(random).iterator(); ++ private boolean attemptPlaceSculk(SculkSpreader sculkspreader, LevelAccessor generatoraccess, BlockPos blockposition, RandomSource randomsource, BlockPos sourceBlock) { // CraftBukkit ++ BlockState iblockdata = generatoraccess.getBlockState(blockposition); ++ TagKey tagkey = sculkspreader.replaceableBlocks(); ++ Iterator iterator = Direction.allShuffled(randomsource).iterator(); + + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + + if (hasFace(iblockdata, enumdirection)) { +- BlockPos blockposition1 = pos.relative(enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ BlockPos blockposition1 = blockposition.relative(enumdirection); ++ BlockState iblockdata1 = generatoraccess.getBlockState(blockposition1); + + if (iblockdata1.is(tagkey)) { + BlockState iblockdata2 = Blocks.SCULK.defaultBlockState(); + +- world.setBlock(blockposition1, iblockdata2, 3); +- Block.pushEntitiesUp(iblockdata1, iblockdata2, world, blockposition1); +- world.playSound((Player) null, blockposition1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F); +- this.veinSpreader.spreadAll(iblockdata2, world, blockposition1, spreadManager.isWorldGeneration()); ++ // CraftBukkit start - Call BlockSpreadEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, sourceBlock, blockposition1, iblockdata2, 3)) { ++ return false; ++ } ++ // CraftBukkit end ++ Block.pushEntitiesUp(iblockdata1, iblockdata2, generatoraccess, blockposition1); ++ generatoraccess.playSound((Player) null, blockposition1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F); ++ this.veinSpreader.spreadAll(iblockdata2, generatoraccess, blockposition1, sculkspreader.isWorldGeneration()); + Direction enumdirection1 = enumdirection.getOpposite(); + Direction[] aenumdirection = SculkVeinBlock.DIRECTIONS; + int i = aenumdirection.length; +@@ -132,10 +137,10 @@ + + if (enumdirection2 != enumdirection1) { + BlockPos blockposition2 = blockposition1.relative(enumdirection2); +- BlockState iblockdata3 = world.getBlockState(blockposition2); ++ BlockState iblockdata3 = generatoraccess.getBlockState(blockposition2); + + if (iblockdata3.is((Block) this)) { +- this.onDischarged(world, iblockdata3, blockposition2, random); ++ this.onDischarged(generatoraccess, iblockdata3, blockposition2, randomsource); + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch new file mode 100644 index 0000000000..ef705416f0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java ++++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java +@@ -98,8 +98,8 @@ + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (world instanceof ServerLevel serverLevel + && world.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity +- && canOpen(state, world, pos, shulkerBoxBlockEntity)) { +- player.openMenu(shulkerBoxBlockEntity); ++ && canOpen(state, world, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check ++ && player.openMenu(shulkerBoxBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.OPEN_SHULKER_BOX); + PiglinAi.angerNearbyPiglins(serverLevel, player, true); + } +@@ -137,7 +137,7 @@ + itemEntity.setDefaultPickUpDelay(); + world.addFreshEntity(itemEntity); + } else { +- shulkerBoxBlockEntity.unpackLootTable(player); ++ shulkerBoxBlockEntity.unpackLootTable(player, true); // Paper - force clear loot table so replenish data isn't persisted in the stack + } + } + +@@ -147,7 +147,15 @@ + @Override + protected List getDrops(BlockState state, LootParams.Builder builder) { + BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY); ++ Runnable reAdd = null; // Paper + if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) { ++ // Paper start - clear loot table if it was already used ++ if (shulkerBoxBlockEntity.lootableData().getLastFill() != -1 || !builder.getLevel().paperConfig().lootables.retainUnlootedShulkerBoxLootTableOnNonPlayerBreak) { ++ net.minecraft.resources.ResourceKey lootTableResourceKey = shulkerBoxBlockEntity.getLootTable(); ++ reAdd = () -> shulkerBoxBlockEntity.setLootTable(lootTableResourceKey); ++ shulkerBoxBlockEntity.setLootTable(null); ++ } ++ // Paper end + builder = builder.withDynamicDrop(CONTENTS, lootConsumer -> { + for (int i = 0; i < shulkerBoxBlockEntity.getContainerSize(); i++) { + lootConsumer.accept(shulkerBoxBlockEntity.getItem(i)); +@@ -155,7 +163,13 @@ + }); + } + ++ // Paper start - re-set loot table if it was cleared ++ try { + return super.getDrops(state, builder); ++ } finally { ++ if (reAdd != null) reAdd.run(); ++ } ++ // Paper end - re-set loot table if it was cleared + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch new file mode 100644 index 0000000000..5dac1e3a2d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch @@ -0,0 +1,58 @@ +--- a/net/minecraft/world/level/block/SignBlock.java ++++ b/net/minecraft/world/level/block/SignBlock.java +@@ -140,7 +140,7 @@ + } else if (flag1) { + return InteractionResult.SUCCESS_SERVER; + } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag)) { +- this.openTextEdit(player, tileentitysign, flag); ++ this.openTextEdit(player, tileentitysign, flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.PASS; +@@ -185,10 +185,36 @@ + return blockpropertywood; + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent + public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) { +- blockEntity.setAllowedPlayerEditor(player.getUUID()); +- player.openTextEdit(blockEntity, front); ++ // Paper start - Add PlayerOpenSignEvent ++ this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN); + } ++ public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) { ++ org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); ++ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos()); ++ org.bukkit.craftbukkit.block.CraftSign bukkitSign = (org.bukkit.craftbukkit.block.CraftSign) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock); ++ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent( ++ bukkitPlayer, ++ bukkitSign, ++ flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK, ++ cause); ++ if (!event.callEvent()) return; ++ if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ final org.bukkit.event.player.PlayerSignOpenEvent.Cause legacyCause = switch (cause) { ++ case PLACE -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE; ++ case PLUGIN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLUGIN; ++ case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT; ++ case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN; ++ }; ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) { ++ // Paper end - Add PlayerOpenSignEvent ++ return; ++ } ++ } // Paper - Add PlayerOpenSignEvent ++ tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID()); ++ entityhuman.openTextEdit(tileentitysign, flag); ++ } + + private boolean otherPlayerIsEditingSign(Player player, SignBlockEntity blockEntity) { + UUID uuid = blockEntity.getPlayerWhoMayEdit(); +@@ -199,6 +225,6 @@ + @Nullable + @Override + public BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType type) { +- return createTickerHelper(type, BlockEntityType.SIGN, SignBlockEntity::tick); ++ return null; // Craftbukkit - remove unnecessary sign ticking + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch new file mode 100644 index 0000000000..e7724b2941 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/SmithingTableBlock.java ++++ b/net/minecraft/world/level/block/SmithingTableBlock.java +@@ -38,8 +38,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_SMITHING_TABLE); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch new file mode 100644 index 0000000000..1d94e2a7cc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/SmokerBlock.java ++++ b/net/minecraft/world/level/block/SmokerBlock.java +@@ -44,8 +44,7 @@ + @Override + protected void openContainer(Level world, BlockPos pos, Player player) { + BlockEntity blockEntity = world.getBlockEntity(pos); +- if (blockEntity instanceof SmokerBlockEntity) { +- player.openMenu((MenuProvider)blockEntity); ++ if (blockEntity instanceof SmokerBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_SMOKER); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch new file mode 100644 index 0000000000..ff63f628fe --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -61,12 +61,31 @@ + return this.getHatchLevel(state) == 2; + } + ++ // Paper start - Call BlockFadeEvent ++ private void rescheduleTick(ServerLevel world, BlockPos pos) { ++ int baseDelay = hatchBoost(world, pos) ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper - Configure sniffer egg hatch time ++ world.scheduleTick(pos, this, (baseDelay / 3) + world.random.nextInt(RANDOM_HATCH_OFFSET_TICKS)); ++ // reschedule to avoid being stuck here and behave like the other calls (see #onPlace) ++ } ++ // Paper end - Call BlockFadeEvent ++ + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!this.isReadyToHatch(state)) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2)) { ++ this.rescheduleTick(world, pos); ++ return; ++ } ++ // Paper end + world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); +- world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2); + } else { ++ // Paper start - Call BlockFadeEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) { ++ this.rescheduleTick(world, pos); ++ return; ++ } ++ // Paper end - Call BlockFadeEvent + world.playSound(null, pos, SoundEvents.SNIFFER_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); + world.destroyBlock(pos, false); + Sniffer sniffer = EntityType.SNIFFER.create(world, EntitySpawnReason.BREEDING); +@@ -74,7 +93,7 @@ + Vec3 vec3 = pos.getCenter(); + sniffer.setBaby(true); + sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F); +- world.addFreshEntity(sniffer); ++ world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + } + } +@@ -86,7 +105,7 @@ + world.levelEvent(3009, pos, 0); + } + +- int i = bl ? 12000 : 24000; ++ int i = bl ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper + int j = i / 3; + world.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(state)); + world.scheduleTick(pos, this, j + world.random.nextInt(300)); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch new file mode 100644 index 0000000000..5db7dbeea9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/SnowLayerBlock.java ++++ b/net/minecraft/world/level/block/SnowLayerBlock.java +@@ -99,6 +99,11 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (world.getBrightness(LightLayer.BLOCK, pos) > 11) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + dropResources(state, world, pos); + world.removeBlock(pos, false); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch new file mode 100644 index 0000000000..8f4ea72cba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/block/SpawnerBlock.java ++++ b/net/minecraft/world/level/block/SpawnerBlock.java +@@ -45,12 +45,20 @@ + @Override + protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + super.spawnAfterBreak(state, world, pos, tool, dropExperience); +- if (dropExperience) { +- int i = 15 + world.random.nextInt(15) + world.random.nextInt(15); ++ // CraftBukkit start - Delegate to getExpDrop ++ } + +- this.popExperience(world, pos, i); ++ @Override ++ public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { ++ if (flag) { ++ int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); ++ ++ // this.popExperience(worldserver, blockposition, i); ++ return i; + } + ++ return 0; ++ // CraftBukkit end + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch new file mode 100644 index 0000000000..d0c197c400 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch @@ -0,0 +1,114 @@ +--- a/net/minecraft/world/level/block/SpongeBlock.java ++++ b/net/minecraft/world/level/block/SpongeBlock.java +@@ -15,6 +15,13 @@ + import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.redstone.Orientation; + ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.craftbukkit.block.CraftBlockState; ++import org.bukkit.craftbukkit.util.BlockStateListPopulator; ++import org.bukkit.event.block.SpongeAbsorbEvent; ++// CraftBukkit end ++ + public class SpongeBlock extends Block { + + public static final MapCodec CODEC = simpleCodec(SpongeBlock::new); +@@ -53,7 +60,8 @@ + } + + private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) { +- return BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> { ++ BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator ++ BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> { + Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS; + int i = aenumdirection.length; + +@@ -67,8 +75,10 @@ + if (blockposition1.equals(pos)) { + return BlockPos.TraversalNodeStatus.ACCEPT; + } else { +- BlockState iblockdata = world.getBlockState(blockposition1); +- FluidState fluid = world.getFluidState(blockposition1); ++ // CraftBukkit start ++ BlockState iblockdata = blockList.getBlockState(blockposition1); ++ FluidState fluid = blockList.getFluidState(blockposition1); ++ // CraftBukkit end + + if (!fluid.is(FluidTags.WATER)) { + return BlockPos.TraversalNodeStatus.SKIP; +@@ -78,27 +88,68 @@ + if (block instanceof BucketPickup) { + BucketPickup ifluidsource = (BucketPickup) block; + +- if (!ifluidsource.pickupBlock((Player) null, world, blockposition1, iblockdata).isEmpty()) { ++ if (!ifluidsource.pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) { // CraftBukkit + return BlockPos.TraversalNodeStatus.ACCEPT; + } + } + + if (iblockdata.getBlock() instanceof LiquidBlock) { +- world.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); ++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit + } else { + if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) { + return BlockPos.TraversalNodeStatus.SKIP; + } + +- BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null; ++ // CraftBukkit start ++ // TileEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null; + +- dropResources(iblockdata, world, blockposition1, tileentity); +- world.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); ++ // dropResources(iblockdata, world, blockposition1, tileentity); ++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); ++ // CraftBukkit end + } + + return BlockPos.TraversalNodeStatus.ACCEPT; + } + } +- }) > 1; ++ }); ++ // CraftBukkit start ++ List blocks = blockList.getList(); // Is a clone ++ if (!blocks.isEmpty()) { ++ final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ SpongeAbsorbEvent event = new SpongeAbsorbEvent(bblock, (List) (List) blocks); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ for (CraftBlockState block : blocks) { ++ BlockPos blockposition1 = block.getPosition(); ++ BlockState iblockdata = world.getBlockState(blockposition1); ++ FluidState fluid = world.getFluidState(blockposition1); ++ ++ if (fluid.is(FluidTags.WATER)) { ++ if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) { ++ // NOP ++ } else if (iblockdata.getBlock() instanceof LiquidBlock) { ++ // NOP ++ } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) { ++ BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null; ++ ++ // Paper start - Fix SpongeAbsortEvent handling ++ if (block.getHandle().isAir()) { ++ dropResources(iblockdata, world, blockposition1, tileentity); ++ } ++ // Paper end - Fix SpongeAbsortEvent handling ++ } ++ } ++ world.setBlock(blockposition1, block.getHandle(), block.getFlag()); ++ } ++ ++ return true; ++ } ++ return false; ++ // CraftBukkit end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch new file mode 100644 index 0000000000..5777f59736 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java ++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +@@ -43,7 +43,13 @@ + + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (this instanceof GrassBlock && world.paperConfig().tickRates.grassSpread != 1 && (world.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks + if (!SpreadingSnowyDirtBlock.canBeGrass(state, world, pos)) { ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.setBlockAndUpdate(pos, Blocks.DIRT.defaultBlockState()); + } else { + if (world.getMaxLocalRawBrightness(pos.above()) >= 9) { +@@ -53,7 +59,7 @@ + BlockPos blockposition1 = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1); + + if (world.getBlockState(blockposition1).is(Blocks.DIRT) && SpreadingSnowyDirtBlock.canPropagate(iblockdata1, world, blockposition1)) { +- world.setBlockAndUpdate(blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(world.getBlockState(blockposition1.above())))); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(world.getBlockState(blockposition1.above())))); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch new file mode 100644 index 0000000000..625e104922 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch @@ -0,0 +1,47 @@ +--- a/net/minecraft/world/level/block/StemBlock.java ++++ b/net/minecraft/world/level/block/StemBlock.java +@@ -26,6 +26,7 @@ + import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class StemBlock extends BushBlock implements BonemealableBlock { + +@@ -74,12 +75,12 @@ + if (world.getRawBrightness(pos, 0) >= 9) { + float f = CropBlock.getGrowthSpeed(this, world, pos); + +- if (random.nextInt((int) (25.0F / f) + 1) == 0) { ++ if (random.nextFloat() < ((this == Blocks.PUMPKIN_STEM ? world.spigotConfig.pumpkinModifier : world.spigotConfig.melonModifier) / (100.0f * (Math.floor((25.0F / f) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution + int i = (Integer) state.getValue(StemBlock.AGE); + + if (i < 7) { + state = (BlockState) state.setValue(StemBlock.AGE, i + 1); +- world.setBlock(pos, state, 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } else { + Direction enumdirection = Direction.Plane.HORIZONTAL.getRandomDirection(random); + BlockPos blockposition1 = pos.relative(enumdirection); +@@ -91,7 +92,11 @@ + Optional optional1 = iregistry.getOptional(this.attachedStem); + + if (optional.isPresent() && optional1.isPresent()) { +- world.setBlockAndUpdate(blockposition1, ((Block) optional.get()).defaultBlockState()); ++ // CraftBukkit start ++ if (!CraftEventFactory.handleBlockGrowEvent(world, blockposition1, ((Block) optional.get()).defaultBlockState())) { ++ return; ++ } ++ // CraftBukkit end + world.setBlockAndUpdate(pos, (BlockState) ((Block) optional1.get()).defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, enumdirection)); + } + } +@@ -121,7 +126,7 @@ + int i = Math.min(7, (Integer) state.getValue(StemBlock.AGE) + Mth.nextInt(world.random, 2, 5)); + BlockState iblockdata1 = (BlockState) state.setValue(StemBlock.AGE, i); + +- world.setBlock(pos, iblockdata1, 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, iblockdata1, 2); // CraftBukkit + if (i == 7) { + iblockdata1.randomTick(world, pos, world.random); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch new file mode 100644 index 0000000000..33e97daa48 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/level/block/StonecutterBlock.java ++++ b/net/minecraft/world/level/block/StonecutterBlock.java +@@ -48,8 +48,9 @@ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- player.openMenu(state.getMenuProvider(world, pos)); ++ if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.INTERACT_WITH_STONECUTTER); ++ } // Paper - Fix InventoryOpenEvent cancellation + } + + return InteractionResult.SUCCESS; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch new file mode 100644 index 0000000000..903b77b2e2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/block/SugarCaneBlock.java ++++ b/net/minecraft/world/level/block/SugarCaneBlock.java +@@ -59,13 +59,14 @@ + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth heigh + int j = (Integer) state.getValue(SugarCaneBlock.AGE); + +- if (j == 15) { +- world.setBlockAndUpdate(pos.above(), this.defaultBlockState()); ++ int modifier = world.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution ++ if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos.above(), this.defaultBlockState()); // CraftBukkit + world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, 0), 4); +- } else { ++ } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution + world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, j + 1), 4); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch new file mode 100644 index 0000000000..c0fadbe893 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch @@ -0,0 +1,62 @@ +--- a/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -28,6 +28,12 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import java.util.Collections; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.player.PlayerHarvestBlockEvent; ++// CraftBukkit end + + public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock { + +@@ -67,10 +73,10 @@ + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + int i = (Integer) state.getValue(SweetBerryBushBlock.AGE); + +- if (i < 3 && random.nextInt(5) == 0 && world.getRawBrightness(pos.above(), 0) >= 9) { ++ if (i < 3 && random.nextFloat() < (world.spigotConfig.sweetBerryModifier / (100.0f * 5)) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution + BlockState iblockdata1 = (BlockState) state.setValue(SweetBerryBushBlock.AGE, i + 1); + +- world.setBlock(pos, iblockdata1, 2); ++ if (!CraftEventFactory.handleBlockGrowEvent(world, pos, iblockdata1, 2)) return; // CraftBukkit + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1)); + } + +@@ -78,6 +84,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { + entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); + if (world instanceof ServerLevel) { +@@ -91,7 +98,7 @@ + double d1 = Math.abs(vec3d.z()); + + if (d0 >= 0.003000000026077032D || d1 >= 0.003000000026077032D) { +- entity.hurtServer(worldserver, world.damageSources().sweetBerryBush(), 1.0F); ++ entity.hurtServer(worldserver, world.damageSources().sweetBerryBush().directBlock(world, pos), 1.0F); // CraftBukkit + } + } + +@@ -118,7 +125,15 @@ + if (i > 1) { + int j = 1 + world.random.nextInt(2); + +- popResource(world, pos, new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0))); ++ // CraftBukkit start - useWithoutItem is always MAIN_HAND ++ PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(world, pos, player, InteractionHand.MAIN_HAND, Collections.singletonList(new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0)))); ++ if (event.isCancelled()) { ++ return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block ++ } ++ for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) { ++ popResource(world, pos, CraftItemStack.asNMSCopy(itemStack)); ++ } ++ // CraftBukkit end + world.playSound((Player) null, pos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + world.random.nextFloat() * 0.4F); + BlockState iblockdata1 = (BlockState) state.setValue(SweetBerryBushBlock.AGE, 1); + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch new file mode 100644 index 0000000000..5478fd0968 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/world/level/block/TargetBlock.java ++++ b/net/minecraft/world/level/block/TargetBlock.java +@@ -42,6 +42,10 @@ + @Override + protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { + int i = updateRedstoneOutput(world, state, hit, projectile); ++ // Paper start - Add TargetHitEvent ++ } ++ private static void awardTargetHitCriteria(Projectile projectile, BlockHitResult hit, int i) { ++ // Paper end - Add TargetHitEvent + if (projectile.getOwner() instanceof ServerPlayer serverPlayer) { + serverPlayer.awardStat(Stats.TARGET_HIT); + CriteriaTriggers.TARGET_BLOCK_HIT.trigger(serverPlayer, projectile, hit.getLocation(), i); +@@ -51,10 +55,31 @@ + private static int updateRedstoneOutput(LevelAccessor world, BlockState state, BlockHitResult hitResult, Entity entity) { + int i = getRedstoneStrength(hitResult, hitResult.getLocation()); + int j = entity instanceof AbstractArrow ? 20 : 8; ++ // Paper start - Add TargetHitEvent ++ boolean shouldAward = false; ++ if (entity instanceof Projectile) { ++ final Projectile projectile = (Projectile) entity; ++ final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, hitResult.getBlockPos()); ++ final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(hitResult.getDirection()); ++ final io.papermc.paper.event.block.TargetHitEvent targetHitEvent = new io.papermc.paper.event.block.TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i); ++ if (targetHitEvent.callEvent()) { ++ i = targetHitEvent.getSignalStrength(); ++ shouldAward = true; ++ } else { ++ return i; ++ } ++ } ++ // Paper end - Add TargetHitEvent + if (!world.getBlockTicks().hasScheduledTick(hitResult.getBlockPos(), state.getBlock())) { + setOutputPower(world, state, i, hitResult.getBlockPos(), j); + } + ++ // Paper start - Award Hit Criteria after Block Update ++ if (shouldAward) { ++ awardTargetHitCriteria((Projectile) entity, hitResult, i); ++ } ++ // Paper end - Award Hit Criteria after Block Update ++ + return i; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch new file mode 100644 index 0000000000..42cf7a6881 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch @@ -0,0 +1,102 @@ +--- a/net/minecraft/world/level/block/TntBlock.java ++++ b/net/minecraft/world/level/block/TntBlock.java +@@ -28,6 +28,10 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.redstone.Orientation; + import net.minecraft.world.phys.BlockHitResult; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.block.TNTPrimeEvent.PrimeCause; ++// CraftBukkit end + + public class TntBlock extends Block { + +@@ -47,7 +51,13 @@ + @Override + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock())) { +- if (world.hasNeighborSignal(pos)) { ++ if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos); + world.removeBlock(pos, false); + } +@@ -57,7 +67,13 @@ + + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { +- if (world.hasNeighborSignal(pos)) { ++ if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos); + world.removeBlock(pos, false); + } +@@ -66,7 +82,7 @@ + + @Override + public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { +- if (!world.isClientSide() && !player.isCreative() && (Boolean) state.getValue(TntBlock.UNSTABLE)) { ++ if (!world.isClientSide() && !player.isCreative() && (Boolean) state.getValue(TntBlock.UNSTABLE) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.BLOCK_BREAK, player, null)) { // CraftBukkit - TNTPrimeEvent + TntBlock.explode(world, pos); + } + +@@ -75,6 +91,13 @@ + + @Override + public void wasExploded(ServerLevel world, BlockPos pos, Explosion explosion) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.entity.Entity source = explosion.getDirectSourceEntity() != null ? explosion.getDirectSourceEntity().getBukkitEntity() : null; ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + PrimedTnt entitytntprimed = new PrimedTnt(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, explosion.getIndirectSourceEntity()); + int i = entitytntprimed.getFuse(); + +@@ -101,6 +124,17 @@ + if (!stack.is(Items.FLINT_AND_STEEL) && !stack.is(Items.FIRE_CHARGE)) { + return super.useItemOn(stack, state, world, pos, player, hand, hit); + } else { ++ // CraftBukkit start - TNTPrimeEvent ++ if (!CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.PLAYER, player, null)) { ++ return InteractionResult.CONSUME; ++ } ++ // CraftBukkit end ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos, player); + world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11); + Item item = stack.getItem(); +@@ -123,6 +157,17 @@ + Entity entity = projectile.getOwner(); + + if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition)) { ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state ++ return; ++ } ++ // CraftBukkit end ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null); + world.removeBlock(blockposition, false); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch new file mode 100644 index 0000000000..0f858bdcd3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/level/block/TrapDoorBlock.java ++++ b/net/minecraft/world/level/block/TrapDoorBlock.java +@@ -37,6 +37,7 @@ + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + + public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock { + +@@ -143,7 +144,39 @@ + boolean flag1 = world.hasNeighborSignal(pos); + + if (flag1 != (Boolean) state.getValue(TrapDoorBlock.POWERED)) { +- if ((Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1) { ++ // CraftBukkit start ++ org.bukkit.World bworld = world.getWorld(); ++ org.bukkit.block.Block bblock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ int power = bblock.getBlockPower(); ++ int oldPower = (Boolean) state.getValue(TrapDoorBlock.OPEN) ? 15 : 0; ++ ++ if (oldPower == 0 ^ power == 0 || sourceBlock.defaultBlockState().isSignalSource()) { ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bblock, oldPower, power); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ flag1 = eventRedstone.getNewCurrent() > 0; ++ } ++ // CraftBukkit end ++ // Paper start - break redstone on trapdoors early ++ boolean open = (Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1; ++ // note: this must run before any state for this block/its neighborus are written to the world ++ // we allow the redstone event to fire so that plugins can block ++ if (flag1 && open) { // if we are now powered and it caused the trap door to open ++ // in this case, first check for the redstone on top first ++ BlockPos abovePos = pos.above(); ++ BlockState above = world.getBlockState(abovePos); ++ if (above.getBlock() instanceof RedStoneWireBlock) { ++ world.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS); ++ Block.popResource(world, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE)); ++ // now check that this didn't change our state ++ if (world.getBlockState(pos) != state) { ++ // our state was changed, so we cannot propagate this update ++ return; ++ } ++ } ++ } ++ if (open) { ++ // Paper end - break redstone on trapdoors early + state = (BlockState) state.setValue(TrapDoorBlock.OPEN, flag1); + this.playSound((Player) null, world, pos, flag1); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch new file mode 100644 index 0000000000..b081063a3f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch @@ -0,0 +1,114 @@ +--- a/net/minecraft/world/level/block/TripWireBlock.java ++++ b/net/minecraft/world/level/block/TripWireBlock.java +@@ -28,6 +28,7 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit + + public class TripWireBlock extends Block { + +@@ -67,6 +68,7 @@ + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return this.defaultBlockState(); // Paper - place tripwire without updating + Level world = ctx.getLevel(); + BlockPos blockposition = ctx.getClickedPos(); + +@@ -75,11 +77,13 @@ + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent tripwire from updating + return direction.getAxis().isHorizontal() ? (BlockState) state.setValue((Property) TripWireBlock.PROPERTY_BY_DIRECTION.get(direction), this.shouldConnectTo(neighborState, direction)) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); + } + + @Override + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + if (!oldState.is(state.getBlock())) { + this.updateSource(world, pos, state); + } +@@ -87,6 +91,7 @@ + + @Override + protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + if (!moved && !state.is(newState.getBlock())) { + this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true)); + } +@@ -94,6 +99,7 @@ + + @Override + public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent disarming tripwires + if (!world.isClientSide && !player.getMainHandItem().isEmpty() && player.getMainHandItem().is(Items.SHEARS)) { + world.setBlock(pos, (BlockState) state.setValue(TripWireBlock.DISARMED, true), 4); + world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); +@@ -103,6 +109,7 @@ + } + + private void updateSource(Level world, BlockPos pos, BlockState state) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST}; + int i = aenumdirection.length; + int j = 0; +@@ -140,6 +147,8 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwires from detecting collision ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos, List.of(entity)); +@@ -149,6 +158,7 @@ + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwire pressed check + if ((Boolean) world.getBlockState(pos).getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos); + } +@@ -179,6 +189,40 @@ + } + } + ++ // CraftBukkit start - Call interact even when triggering connected tripwire ++ if (flag != flag1 && flag1 && (Boolean)iblockdata.getValue(TripWireBlock.ATTACHED)) { ++ org.bukkit.World bworld = world.getWorld(); ++ org.bukkit.plugin.PluginManager manager = world.getCraftServer().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 : entities) { ++ if (object != null) { ++ org.bukkit.event.Cancellable cancellable; ++ ++ if (object instanceof Player) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) 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) { + iblockdata = (BlockState) iblockdata.setValue(TripWireBlock.POWERED, flag1); + world.setBlock(pos, iblockdata, 3); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch new file mode 100644 index 0000000000..85f745ec8f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -31,6 +31,10 @@ + import net.minecraft.world.level.redstone.Orientation; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++// CraftBukkit end + + public class TripWireHookBlock extends Block { + +@@ -174,10 +178,20 @@ + world.setBlock(blockposition1, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection1), 3); + TripWireHookBlock.notifyNeighbors(block, world, blockposition1, enumdirection1); + TripWireHookBlock.emitState(world, blockposition1, flag4, flag5, flag2, flag3); ++ } ++ ++ // CraftBukkit start ++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), 15, 0); ++ world.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ ++ if (eventRedstone.getNewCurrent() > 0) { ++ return; + } ++ // CraftBukkit end + + TripWireHookBlock.emitState(world, pos, flag4, flag5, flag2, flag3); + if (!flag) { ++ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update + world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); + if (flag1) { + TripWireHookBlock.notifyNeighbors(block, world, pos, enumdirection); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch new file mode 100644 index 0000000000..c27f68ac72 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/level/block/TurtleEggBlock.java ++++ b/net/minecraft/world/level/block/TurtleEggBlock.java +@@ -31,6 +31,11 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityInteractEvent; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class TurtleEggBlock extends Block { + +@@ -74,6 +79,19 @@ + private void destroyEgg(Level world, BlockState state, BlockPos pos, Entity entity, int inverseChance) { + if (state.is(Blocks.TURTLE_EGG) && world instanceof ServerLevel worldserver) { + if (this.canDestroyEgg(worldserver, entity) && world.random.nextInt(inverseChance) == 0) { ++ // CraftBukkit start - Step on eggs ++ org.bukkit.event.Cancellable cancellable; ++ if (entity instanceof Player) { ++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), CraftBlock.at(worldserver, pos)); ++ worldserver.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable); ++ } ++ ++ if (cancellable.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.decreaseEggs(worldserver, pos, state); + } + } +@@ -100,10 +118,20 @@ + int i = (Integer) state.getValue(TurtleEggBlock.HATCH); + + if (i < 2) { ++ // CraftBukkit start - Call BlockGrowEvent ++ if (!CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(TurtleEggBlock.HATCH, i + 1), 2)) { ++ return; ++ } ++ // CraftBukkit end + world.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); +- world.setBlock(pos, (BlockState) state.setValue(TurtleEggBlock.HATCH, i + 1), 2); ++ // worldserver.setBlock(blockposition, (IBlockData) iblockdata.setValue(BlockTurtleEgg.HATCH, i + 1), 2); // CraftBukkit - handled above + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); + } else { ++ // CraftBukkit start - Call BlockFadeEvent ++ if (CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + world.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); + world.removeBlock(pos, false); + world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(state)); +@@ -116,7 +144,7 @@ + entityturtle.setAge(-24000); + entityturtle.setHomePos(pos); + entityturtle.moveTo((double) pos.getX() + 0.3D + (double) j * 0.2D, (double) pos.getY(), (double) pos.getZ() + 0.3D, 0.0F, 0.0F); +- world.addFreshEntity(entityturtle); ++ world.addFreshEntity(entityturtle, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit + } + } + } +@@ -147,8 +175,8 @@ + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + this.decreaseEggs(world, pos, state); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch new file mode 100644 index 0000000000..0403549dc8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch @@ -0,0 +1,79 @@ +--- a/net/minecraft/world/level/block/VineBlock.java ++++ b/net/minecraft/world/level/block/VineBlock.java +@@ -24,6 +24,7 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit + + public class VineBlock extends Block { + +@@ -184,7 +185,7 @@ + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (world.getGameRules().getBoolean(GameRules.RULE_DO_VINES_SPREAD)) { +- if (random.nextInt(4) == 0) { ++ if (random.nextFloat() < (world.spigotConfig.vineModifier / (100.0f * 4))) { // Spigot - SPIGOT-7159: Better modifier resolution + Direction enumdirection = Direction.getRandom(random); + BlockPos blockposition1 = pos.above(); + BlockPos blockposition2; +@@ -203,30 +204,34 @@ + BlockPos blockposition3 = blockposition2.relative(enumdirection1); + BlockPos blockposition4 = blockposition2.relative(enumdirection2); + ++ // CraftBukkit start - Call BlockSpreadEvent ++ BlockPos source = pos; ++ + if (flag && VineBlock.isAcceptableNeighbour(world, blockposition3, enumdirection1)) { +- world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection1), true), 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection1), true), 2); + } else if (flag1 && VineBlock.isAcceptableNeighbour(world, blockposition4, enumdirection2)) { +- world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection2), true), 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection2), true), 2); + } else { + Direction enumdirection3 = enumdirection.getOpposite(); + + if (flag && world.isEmptyBlock(blockposition3) && VineBlock.isAcceptableNeighbour(world, pos.relative(enumdirection1), enumdirection3)) { +- world.setBlock(blockposition3, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition3, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2); + } else if (flag1 && world.isEmptyBlock(blockposition4) && VineBlock.isAcceptableNeighbour(world, pos.relative(enumdirection2), enumdirection3)) { +- world.setBlock(blockposition4, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition4, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2); + } else if ((double) random.nextFloat() < 0.05D && VineBlock.isAcceptableNeighbour(world, blockposition2.above(), Direction.UP)) { +- world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.UP, true), 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.UP, true), 2); + } ++ // CraftBukkit end + } + } else if (VineBlock.isAcceptableNeighbour(world, blockposition2, enumdirection)) { +- world.setBlock(pos, (BlockState) state.setValue(VineBlock.getPropertyForFace(enumdirection), true), 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(VineBlock.getPropertyForFace(enumdirection), true), 2); // CraftBukkit + } + + } + } else { + if (enumdirection == Direction.UP && pos.getY() < world.getMaxY()) { + if (this.canSupportAtFace(world, pos, enumdirection)) { +- world.setBlock(pos, (BlockState) state.setValue(VineBlock.UP, true), 2); ++ CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(VineBlock.UP, true), 2); // CraftBukkit + return; + } + +@@ -246,7 +251,7 @@ + } + + if (this.hasHorizontalConnection(iblockdata2)) { +- world.setBlock(blockposition1, iblockdata2, 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, iblockdata2, 2); // CraftBukkit + } + + return; +@@ -261,7 +266,7 @@ + BlockState iblockdata4 = this.copyRandomFaces(state, iblockdata3, random); + + if (iblockdata3 != iblockdata4 && this.hasHorizontalConnection(iblockdata4)) { +- world.setBlock(blockposition2, iblockdata4, 2); ++ CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, iblockdata4, 2); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch new file mode 100644 index 0000000000..73be8c6267 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/WallHangingSignBlock.java ++++ b/net/minecraft/world/level/block/WallHangingSignBlock.java +@@ -179,6 +179,6 @@ + @Nullable + @Override + public BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType type) { +- return createTickerHelper(type, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick); ++ return null; // Craftbukkit - remove unnecessary sign ticking + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch new file mode 100644 index 0000000000..279e050da1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -13,6 +13,9 @@ + import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end + + public class WaterlilyBlock extends BushBlock { + +@@ -30,8 +33,14 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + super.entityInside(state, world, pos, entity); + if (world instanceof ServerLevel && entity instanceof AbstractBoat) { ++ // CraftBukkit start ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state ++ return; ++ } ++ // CraftBukkit end + world.destroyBlock(new BlockPos(pos), true, entity); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch new file mode 100644 index 0000000000..1204a1e128 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/WebBlock.java ++++ b/net/minecraft/world/level/block/WebBlock.java +@@ -24,6 +24,7 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + Vec3 vec3 = new Vec3(0.25, 0.05F, 0.25); + if (entity instanceof LivingEntity livingEntity && livingEntity.hasEffect(MobEffects.WEAVING)) { + vec3 = new Vec3(0.5, 0.25, 0.5); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch new file mode 100644 index 0000000000..67ce37fdf0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java ++++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java +@@ -6,6 +6,7 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; +@@ -13,6 +14,8 @@ + import net.minecraft.world.level.block.state.properties.BlockSetType; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.IntegerProperty; ++import org.bukkit.event.entity.EntityInteractEvent; ++// CraftBukkit end + + public class WeightedPressurePlateBlock extends BasePressurePlateBlock { + +@@ -39,8 +42,28 @@ + + @Override + protected int getSignalStrength(Level world, BlockPos pos) { +- int i = Math.min(getEntityCount(world, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class), this.maxWeight); ++ // CraftBukkit start ++ // int i = Math.min(getEntityCount(world, BlockPressurePlateWeighted.TOUCH_AABB.move(blockposition), Entity.class), this.maxWeight); ++ int i = 0; ++ for (Entity entity : getEntities(world, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class)) { ++ org.bukkit.event.Cancellable cancellable; + ++ if (entity instanceof Player) { ++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null); ++ } else { ++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); ++ world.getCraftServer().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); ++ // CraftBukkit end ++ + if (i > 0) { + float f = (float) Math.min(this.maxWeight, i) / (float) this.maxWeight; + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch new file mode 100644 index 0000000000..c456abf513 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/block/WitherRoseBlock.java ++++ b/net/minecraft/world/level/block/WitherRoseBlock.java +@@ -63,10 +63,11 @@ + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { + if (world.getDifficulty() != Difficulty.PEACEFUL && entity instanceof LivingEntity entityliving) { + if (!entityliving.isInvulnerableTo(worldserver, world.damageSources().wither())) { +- entityliving.addEffect(this.getBeeInteractionEffect()); ++ entityliving.addEffect(this.getBeeInteractionEffect(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WITHER_ROSE); // CraftBukkit + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch new file mode 100644 index 0000000000..e34748cc0e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch @@ -0,0 +1,50 @@ +--- a/net/minecraft/world/level/block/WitherSkullBlock.java ++++ b/net/minecraft/world/level/block/WitherSkullBlock.java +@@ -26,6 +26,10 @@ + import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder; + import net.minecraft.world.level.block.state.predicate.BlockStatePredicate; + ++// CraftBukkit start ++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; ++// CraftBukkit end ++ + public class WitherSkullBlock extends SkullBlock { + + public static final MapCodec CODEC = simpleCodec(WitherSkullBlock::new); +@@ -58,6 +62,7 @@ + } + + public static void checkSpawn(Level world, BlockPos pos, SkullBlockEntity blockEntity) { ++ if (world.captureBlockStates) return; // CraftBukkit + if (!world.isClientSide) { + BlockState iblockdata = blockEntity.getBlockState(); + boolean flag = iblockdata.is(Blocks.WITHER_SKELETON_SKULL) || iblockdata.is(Blocks.WITHER_SKELETON_WALL_SKULL); +@@ -69,12 +74,18 @@ + WitherBoss entitywither = (WitherBoss) EntityType.WITHER.create(world, EntitySpawnReason.TRIGGERED); + + if (entitywither != null) { +- CarvedPumpkinBlock.clearPatternBlocks(world, shapedetector_shapedetectorcollection); ++ // BlockPumpkinCarved.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - move down + BlockPos blockposition1 = shapedetector_shapedetectorcollection.getBlock(1, 2, 0).getPos(); + + entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F); + entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; + entitywither.makeInvulnerable(); ++ // CraftBukkit start ++ if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) { ++ return; ++ } ++ CarvedPumpkinBlock.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - from above ++ // CraftBukkit end + Iterator iterator = world.getEntitiesOfClass(ServerPlayer.class, entitywither.getBoundingBox().inflate(50.0D)).iterator(); + + while (iterator.hasNext()) { +@@ -83,7 +94,7 @@ + CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayer, (Entity) entitywither); + } + +- world.addFreshEntity(entitywither); ++ // world.addFreshEntity(entitywither); // CraftBukkit - moved up + CarvedPumpkinBlock.updatePatternBlocks(world, shapedetector_shapedetectorcollection); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch new file mode 100644 index 0000000000..8438fd826a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch @@ -0,0 +1,356 @@ +--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -22,7 +22,6 @@ + import net.minecraft.world.ContainerHelper; + import net.minecraft.world.WorldlyContainer; + import net.minecraft.world.entity.ExperienceOrb; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.player.StackedItemContents; + import net.minecraft.world.inventory.ContainerData; + import net.minecraft.world.inventory.RecipeCraftingHolder; +@@ -41,6 +40,20 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftItemType; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.entity.Player; ++import org.bukkit.event.block.BlockExpEvent; ++import org.bukkit.event.inventory.FurnaceBurnEvent; ++import org.bukkit.event.inventory.FurnaceExtractEvent; ++import org.bukkit.event.inventory.FurnaceSmeltEvent; ++import org.bukkit.event.inventory.FurnaceStartSmeltEvent; ++import org.bukkit.inventory.CookingRecipe; ++// CraftBukkit end + + public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible { + +@@ -65,6 +78,8 @@ + protected final ContainerData dataAccess; + public final Reference2IntOpenHashMap>> recipesUsed; + private final RecipeManager.CachedCheck quickCheck; ++ public final RecipeType recipeType; // Paper - cook speed multiplier API ++ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API + + protected AbstractFurnaceBlockEntity(BlockEntityType blockEntityType, BlockPos pos, BlockState state, RecipeType recipeType) { + super(blockEntityType, pos, state); +@@ -110,9 +125,40 @@ + } + }; + this.recipesUsed = new Reference2IntOpenHashMap(); +- this.quickCheck = RecipeManager.createCheck(recipeType); ++ this.quickCheck = RecipeManager.createCheck((RecipeType) recipeType); // CraftBukkit - decompile error // Eclipse fail ++ this.recipeType = recipeType; // Paper - cook speed multiplier API + } + ++ // CraftBukkit start - add fields and methods ++ private int maxStack = MAX_STACK; ++ public List transaction = new java.util.ArrayList(); ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + private boolean isLit() { + return this.litTimeRemaining > 0; + } +@@ -132,9 +178,18 @@ + while (iterator.hasNext()) { + String s = (String) iterator.next(); + +- this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)), nbttagcompound1.getInt(s)); ++ // Paper start - Validate ResourceLocation ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(s); ++ if (resourceLocation != null) { ++ this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, resourceLocation), nbttagcompound1.getInt(s)); ++ } + } + ++ // Paper start - cook speed multiplier API ++ if (nbt.contains("Paper.CookSpeedMultiplier")) { ++ this.cookSpeedMultiplier = nbt.getDouble("Paper.CookSpeedMultiplier"); ++ } ++ // Paper end - cook speed multiplier API + } + + @Override +@@ -144,6 +199,7 @@ + nbt.putShort("cooking_total_time", (short) this.cookingTotalTime); + nbt.putShort("lit_time_remaining", (short) this.litTimeRemaining); + nbt.putShort("lit_total_time", (short) this.litTotalTime); ++ nbt.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API + ContainerHelper.saveAllItems(nbt, this.items, registries); + CompoundTag nbttagcompound1 = new CompoundTag(); + +@@ -175,7 +231,7 @@ + RecipeHolder recipeholder; + + if (flag2) { +- recipeholder = (RecipeHolder) blockEntity.quickCheck.getRecipeFor(singlerecipeinput, world).orElse((Object) null); ++ recipeholder = (RecipeHolder) blockEntity.quickCheck.getRecipeFor(singlerecipeinput, world).orElse(null); // CraftBukkit - decompile error + } else { + recipeholder = null; + } +@@ -183,11 +239,22 @@ + int i = blockEntity.getMaxStackSize(); + + if (!blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { +- blockEntity.litTimeRemaining = blockEntity.getBurnDuration(world.fuelValues(), itemstack); ++ // CraftBukkit start ++ CraftItemStack fuel = CraftItemStack.asCraftMirror(itemstack); ++ ++ FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(CraftBlock.at(world, pos), fuel, blockEntity.getBurnDuration(world.fuelValues(), itemstack)); ++ world.getCraftServer().getPluginManager().callEvent(furnaceBurnEvent); ++ ++ if (furnaceBurnEvent.isCancelled()) { ++ return; ++ } ++ ++ blockEntity.litTimeRemaining = furnaceBurnEvent.getBurnTime(); + blockEntity.litTotalTime = blockEntity.litTimeRemaining; +- if (blockEntity.isLit()) { ++ if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) { ++ // CraftBukkit end + flag1 = true; +- if (flag3) { ++ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent + Item item = itemstack.getItem(); + + itemstack.shrink(1); +@@ -199,11 +266,23 @@ + } + + if (blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { ++ // CraftBukkit start ++ if (recipeholder != null && blockEntity.cookingTimer == 0) { ++ CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0)); ++ CookingRecipe recipe = (CookingRecipe) recipeholder.toBukkitRecipe(); ++ ++ FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe, AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier)); // Paper - cook speed multiplier API ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ blockEntity.cookingTotalTime = event.getTotalCookTime(); ++ } ++ // CraftBukkit end ++ + ++blockEntity.cookingTimer; +- if (blockEntity.cookingTimer == blockEntity.cookingTotalTime) { ++ if (blockEntity.cookingTimer >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API + blockEntity.cookingTimer = 0; +- blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity); +- if (AbstractFurnaceBlockEntity.burn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { ++ blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier); // Paper - cook speed multiplier API ++ if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { // CraftBukkit + blockEntity.setRecipeUsed(recipeholder); + } + +@@ -242,20 +321,47 @@ + } + } + +- private static boolean burn(RegistryAccess dynamicRegistryManager, @Nullable RecipeHolder recipe, SingleRecipeInput input, NonNullList inventory, int maxCount) { +- if (recipe != null && AbstractFurnaceBlockEntity.canBurn(dynamicRegistryManager, recipe, input, inventory, maxCount)) { +- ItemStack itemstack = (ItemStack) inventory.get(0); +- ItemStack itemstack1 = ((AbstractCookingRecipe) recipe.value()).assemble(input, dynamicRegistryManager); +- ItemStack itemstack2 = (ItemStack) inventory.get(2); ++ private static boolean burn(Level world, BlockPos blockposition, RegistryAccess iregistrycustom, @Nullable RecipeHolder recipeholder, SingleRecipeInput singlerecipeinput, NonNullList nonnulllist, int i) { // CraftBukkit ++ if (recipeholder != null && AbstractFurnaceBlockEntity.canBurn(iregistrycustom, recipeholder, singlerecipeinput, nonnulllist, i)) { ++ ItemStack itemstack = (ItemStack) nonnulllist.get(0); ++ ItemStack itemstack1 = ((AbstractCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, iregistrycustom); ++ ItemStack itemstack2 = (ItemStack) nonnulllist.get(2); + ++ // CraftBukkit start - fire FurnaceSmeltEvent ++ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); ++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); ++ ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe) recipeholder.toBukkitRecipe()); // Paper - Add recipe to cook events ++ world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); ++ ++ if (furnaceSmeltEvent.isCancelled()) { ++ return false; ++ } ++ ++ result = furnaceSmeltEvent.getResult(); ++ itemstack1 = CraftItemStack.asNMSCopy(result); ++ ++ if (!itemstack1.isEmpty()) { ++ if (itemstack2.isEmpty()) { ++ nonnulllist.set(2, itemstack1.copy()); ++ } else if (CraftItemStack.asCraftMirror(itemstack2).isSimilar(result)) { ++ itemstack2.grow(itemstack1.getCount()); ++ } else { ++ return false; ++ } ++ } ++ ++ /* + if (itemstack2.isEmpty()) { +- inventory.set(2, itemstack1.copy()); ++ nonnulllist.set(2, itemstack1.copy()); + } else if (ItemStack.isSameItemSameComponents(itemstack2, itemstack1)) { + itemstack2.grow(1); + } ++ */ ++ // CraftBukkit end + +- if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) inventory.get(1)).isEmpty() && ((ItemStack) inventory.get(1)).is(Items.BUCKET)) { +- inventory.set(1, new ItemStack(Items.WATER_BUCKET)); ++ if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) nonnulllist.get(1)).isEmpty() && ((ItemStack) nonnulllist.get(1)).is(Items.BUCKET)) { ++ nonnulllist.set(1, new ItemStack(Items.WATER_BUCKET)); + } + + itemstack.shrink(1); +@@ -269,12 +375,14 @@ + return fuelRegistry.burnDuration(stack); + } + +- public static int getTotalCookTime(ServerLevel world, AbstractFurnaceBlockEntity furnace) { ++ public static int getTotalCookTime(@Nullable ServerLevel world, AbstractFurnaceBlockEntity furnace, RecipeType recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API + SingleRecipeInput singlerecipeinput = new SingleRecipeInput(furnace.getItem(0)); + +- return (Integer) furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { +- return ((AbstractCookingRecipe) recipeholder.value()).cookingTime(); +- }).orElse(200); ++ // Paper start - cook speed multiplier API ++ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ ++ int cookTime = world != null ? furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map(holder -> holder.value().cookingTime()).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singlerecipeinput, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(holder -> holder.value().cookingTime()).orElse(200)); ++ return (int) Math.ceil (cookTime / cookSpeedMultiplier); ++ // Paper end - cook speed multiplier API + } + + @Override +@@ -320,12 +428,11 @@ + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this); ++ this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API + this.cookingTimer = 0; + this.setChanged(); + } + } +- + } + + @Override +@@ -358,19 +465,19 @@ + } + + @Override +- public void awardUsedRecipes(Player player, List ingredients) {} ++ public void awardUsedRecipes(net.minecraft.world.entity.player.Player player, List ingredients) {} + +- public void awardUsedRecipesAndPopExperience(ServerPlayer player) { +- List> list = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position()); ++ public void awardUsedRecipesAndPopExperience(ServerPlayer entityplayer, ItemStack itemstack, int amount) { // CraftBukkit ++ List> list = this.getRecipesToAwardAndPopExperience(entityplayer.serverLevel(), entityplayer.position(), this.worldPosition, entityplayer, itemstack, amount); // CraftBukkit + +- player.awardRecipes(list); ++ entityplayer.awardRecipes(list); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + RecipeHolder recipeholder = (RecipeHolder) iterator.next(); + + if (recipeholder != null) { +- player.triggerRecipeCrafted(recipeholder, this.items); ++ entityplayer.triggerRecipeCrafted(recipeholder, this.items); + } + } + +@@ -378,41 +485,56 @@ + } + + public List> getRecipesToAwardAndPopExperience(ServerLevel world, Vec3 pos) { ++ // CraftBukkit start ++ return this.getRecipesToAwardAndPopExperience(world, pos, this.worldPosition, null, null, 0); ++ } ++ ++ public List> getRecipesToAwardAndPopExperience(ServerLevel worldserver, Vec3 vec3d, BlockPos blockposition, ServerPlayer entityplayer, ItemStack itemstack, int amount) { ++ // CraftBukkit end + List> list = Lists.newArrayList(); + ObjectIterator objectiterator = this.recipesUsed.reference2IntEntrySet().iterator(); + + while (objectiterator.hasNext()) { + Entry>> entry = (Entry) objectiterator.next(); + +- world.recipeAccess().byKey((ResourceKey) entry.getKey()).ifPresent((recipeholder) -> { ++ worldserver.recipeAccess().byKey(entry.getKey()).ifPresent((recipeholder) -> { // CraftBukkit - decompile error ++ if (!(recipeholder.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes + list.add(recipeholder); +- AbstractFurnaceBlockEntity.createExperience(world, pos, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).experience()); ++ AbstractFurnaceBlockEntity.createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).experience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit + }); + } + + return list; + } + +- private static void createExperience(ServerLevel world, Vec3 pos, int multiplier, float experience) { +- int j = Mth.floor((float) multiplier * experience); +- float f1 = Mth.frac((float) multiplier * experience); ++ private static void createExperience(ServerLevel worldserver, Vec3 vec3d, int i, float f, BlockPos blockposition, net.minecraft.world.entity.player.Player entityhuman, ItemStack itemstack, int amount) { // CraftBukkit ++ int j = Mth.floor((float) i * f); ++ float f1 = Mth.frac((float) i * f); + + if (f1 != 0.0F && Math.random() < (double) f1) { + ++j; + } + +- ExperienceOrb.award(world, pos, j); ++ // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent ++ BlockExpEvent event; ++ if (amount != 0) { ++ event = new FurnaceExtractEvent((Player) entityhuman.getBukkitEntity(), CraftBlock.at(worldserver, blockposition), CraftItemType.minecraftToBukkit(itemstack.getItem()), amount, j); ++ } else { ++ event = new BlockExpEvent(CraftBlock.at(worldserver, blockposition), j); ++ } ++ worldserver.getCraftServer().getPluginManager().callEvent(event); ++ j = event.getExpToDrop(); ++ // CraftBukkit end ++ ++ ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper + } + + @Override + public void fillStackedContents(StackedItemContents finder) { +- Iterator iterator = this.items.iterator(); ++ // Paper start - don't account fuel stack (fixes MC-243057) ++ finder.accountStack(this.items.get(SLOT_INPUT)); ++ finder.accountStack(this.items.get(SLOT_RESULT)); ++ // Paper end + +- while (iterator.hasNext()) { +- ItemStack itemstack = (ItemStack) iterator.next(); +- +- finder.accountStack(itemstack); +- } +- + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch new file mode 100644 index 0000000000..3b8ce9376c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java +@@ -19,13 +19,17 @@ + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.List; ++// CraftBukkit end ++ + public class BannerBlockEntity extends BlockEntity implements Nameable { + + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int MAX_PATTERNS = 6; + private static final String TAG_PATTERNS = "patterns"; + @Nullable +- private Component name; ++ public Component name; // Paper - public + public DyeColor baseColor; + private BannerPatternLayers patterns; + +@@ -53,7 +57,7 @@ + @Override + protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.saveAdditional(nbt, registries); +- if (!this.patterns.equals(BannerPatternLayers.EMPTY)) { ++ if (!this.patterns.equals(BannerPatternLayers.EMPTY) || serialisingForNetwork.get()) { // Paper - always send patterns to client + nbt.put("patterns", (Tag) BannerPatternLayers.CODEC.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.patterns).getOrThrow()); + } + +@@ -74,7 +78,7 @@ + BannerPatternLayers.CODEC.parse(registries.createSerializationContext(NbtOps.INSTANCE), nbt.get("patterns")).resultOrPartial((s) -> { + BannerBlockEntity.LOGGER.error("Failed to parse banner patterns: '{}'", s); + }).ifPresent((bannerpatternlayers) -> { +- this.patterns = bannerpatternlayers; ++ this.setPatterns(bannerpatternlayers); // CraftBukkit - apply limits + }); + } + +@@ -85,9 +89,18 @@ + return ClientboundBlockEntityDataPacket.create(this); + } + ++ // Paper start - always send patterns to client ++ ThreadLocal serialisingForNetwork = ThreadLocal.withInitial(() -> Boolean.FALSE); + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { ++ final Boolean wasSerialisingForNetwork = serialisingForNetwork.get(); ++ try { ++ serialisingForNetwork.set(Boolean.TRUE); + return this.saveWithoutMetadata(registries); ++ } finally { ++ serialisingForNetwork.set(wasSerialisingForNetwork); ++ } ++ // Paper end - always send patterns to client + } + + public BannerPatternLayers getPatterns() { +@@ -108,7 +121,7 @@ + @Override + protected void applyImplicitComponents(BlockEntity.DataComponentInput components) { + super.applyImplicitComponents(components); +- this.patterns = (BannerPatternLayers) components.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY); ++ this.setPatterns((BannerPatternLayers) components.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY)); // CraftBukkit - apply limits + this.name = (Component) components.get(DataComponents.CUSTOM_NAME); + } + +@@ -124,4 +137,13 @@ + nbt.remove("patterns"); + nbt.remove("CustomName"); + } ++ ++ // CraftBukkit start ++ public void setPatterns(BannerPatternLayers bannerpatternlayers) { ++ if (bannerpatternlayers.layers().size() > 20) { ++ bannerpatternlayers = new BannerPatternLayers(List.copyOf(bannerpatternlayers.layers().subList(0, 20))); ++ } ++ this.patterns = bannerpatternlayers; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch new file mode 100644 index 0000000000..bc89832782 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +@@ -20,9 +20,49 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.BarrelBlock; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.List; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class BarrelBlockEntity extends RandomizableContainerBlockEntity { + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new ArrayList<>(); ++ private int maxStack = MAX_STACK; ++ ++ @Override ++ public List getContents() { ++ return this.items; ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ @Override ++ public void setMaxStackSize(int i) { ++ this.maxStack = i; ++ } ++ // CraftBukkit end + private NonNullList items; + public final ContainerOpenersCounter openersCounter; + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch new file mode 100644 index 0000000000..07ed6af225 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch @@ -0,0 +1,62 @@ +--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -73,17 +73,44 @@ + protected abstract Component getDefaultName(); + + public boolean canOpen(Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()); ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent + public static boolean canUnlock(Player player, LockCode lock, Component containerName) { ++ // Paper start - Add BlockLockCheckEvent ++ return canUnlock(player, lock, containerName, null); ++ } ++ public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { ++ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { ++ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); ++ net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName)); ++ net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F); ++ final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, serverPlayer.getBukkitEntity(), lockedMessage, lockedSound); ++ event.callEvent(); ++ if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) { ++ return true; ++ } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) { ++ if (event.getLockedMessage() != null) { ++ event.getPlayer().sendActionBar(event.getLockedMessage()); ++ } ++ if (event.getLockedSound() != null) { ++ event.getPlayer().playSound(event.getLockedSound()); ++ } ++ return false; ++ } else { ++ return true; ++ } ++ } else { // logic below is replaced by logic above ++ // Paper end - Add BlockLockCheckEvent + if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) { +- player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); ++ player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change + player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F); + return false; + } else { + return true; + } ++ } // Paper - Add BlockLockCheckEvent + } + + protected abstract NonNullList getItems(); +@@ -178,4 +205,12 @@ + nbt.remove("lock"); + nbt.remove("Items"); + } ++ ++ // CraftBukkit start ++ @Override ++ public org.bukkit.Location getLocation() { ++ if (this.level == null) return null; ++ return new org.bukkit.Location(this.level.getWorld(), this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch new file mode 100644 index 0000000000..736be5da6c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch @@ -0,0 +1,259 @@ +--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.level.block.entity; + ++import com.destroystokyo.paper.event.block.BeaconEffectEvent; + import com.google.common.collect.ImmutableList; + import com.google.common.collect.Lists; + import java.util.Collection; +@@ -45,6 +46,11 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.levelgen.Heightmap; + import net.minecraft.world.phys.AABB; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.potion.CraftPotionUtil; ++import org.bukkit.potion.PotionEffect; ++// CraftBukkit end + + public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Nameable { + +@@ -71,7 +77,36 @@ + public Component name; + public LockCode lockKey; + private final ContainerData dataAccess; ++ // CraftBukkit start - add fields and methods ++ public PotionEffect getPrimaryEffect() { ++ return (this.primaryPower != null) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.primaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; ++ } + ++ public PotionEffect getSecondaryEffect() { ++ return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; ++ } ++ // CraftBukkit end ++ // Paper start - Custom beacon ranges ++ private final String PAPER_RANGE_TAG = "Paper.Range"; ++ private double effectRange = -1; ++ ++ public double getEffectRange() { ++ if (this.effectRange < 0) { ++ return this.levels * 10 + 10; ++ } else { ++ return effectRange; ++ } ++ } ++ ++ public void setEffectRange(double range) { ++ this.effectRange = range; ++ } ++ ++ public void resetEffectRange() { ++ this.effectRange = -1; ++ } ++ // Paper end - Custom beacon ranges ++ + @Nullable + static Holder filterEffect(@Nullable Holder effect) { + return BeaconBlockEntity.VALID_EFFECTS.contains(effect) ? effect : null; +@@ -186,10 +221,19 @@ + } + + if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { +- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); ++ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } ++ // Paper start - beacon activation/deactivation events ++ if (i1 <= 0 && blockEntity.levels > 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); ++ } else if (i1 > 0 && blockEntity.levels <= 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ } ++ // Paper end - beacon activation/deactivation events + + if (blockEntity.lastCheckY >= l) { + blockEntity.lastCheckY = world.getMinY() - 1; +@@ -247,43 +291,123 @@ + + @Override + public void setRemoved() { ++ // Paper start - beacon activation/deactivation events ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ // Paper end - beacon activation/deactivation events ++ // Paper start - fix MC-153086 ++ if (this.levels > 0 && !this.beamSections.isEmpty()) { + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); ++ } ++ // Paper end + super.setRemoved(); + } + +- private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect) { +- if (!world.isClientSide && primaryEffect != null) { +- double d0 = (double) (beaconLevel * 10 + 10); ++ // CraftBukkit start - split into components ++ private static byte getAmplification(int i, @Nullable Holder holder, @Nullable Holder holder1) { ++ { + byte b0 = 0; + +- if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) { ++ if (i >= 4 && Objects.equals(holder, holder1)) { + b0 = 1; + } + +- int j = (9 + beaconLevel * 2) * 20; +- AABB axisalignedbb = (new AABB(pos)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); +- List list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ return b0; ++ } ++ } ++ ++ private static int getLevel(int i) { ++ { ++ int j = (9 + i * 2) * 20; ++ return j; ++ } ++ } ++ ++ public static List getHumansInRange(Level world, BlockPos blockposition, int i) { ++ // Paper start - Custom beacon ranges ++ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); ++ } ++ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges ++ { ++ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges ++ ++ AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); ++ // Paper start - Perf: optimize player lookup for beacons ++ List list; ++ if (d0 <= 128.0) { ++ list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ } else { ++ list = new java.util.ArrayList<>(); ++ for (Player player : world.players()) { ++ if (player.isSpectator()) { ++ continue; ++ } ++ if (player.getBoundingBox().intersects(axisalignedbb)) { ++ list.add(player); ++ } ++ } ++ } ++ // Paper end - Perf: optimize player lookup for beacons ++ ++ return list; ++ } ++ } ++ ++ private static void applyEffect(List list, @Nullable Holder holder, int j, int b0, boolean isPrimary, BlockPos worldPosition) { // Paper - BeaconEffectEvent ++ if (!list.isEmpty()) { // Paper - BeaconEffectEvent + Iterator iterator = list.iterator(); + + Player entityhuman; ++ // Paper start - BeaconEffectEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(((Player) list.get(0)).level(), worldPosition); ++ PotionEffect effect = CraftPotionUtil.toBukkit(new MobEffectInstance(holder, j, b0, true, true)); ++ // Paper end - BeaconEffectEvent + + while (iterator.hasNext()) { +- entityhuman = (Player) iterator.next(); +- entityhuman.addEffect(new MobEffectInstance(primaryEffect, j, b0, true, true)); ++ // Paper start - BeaconEffectEvent ++ entityhuman = (ServerPlayer) iterator.next(); ++ BeaconEffectEvent event = new BeaconEffectEvent(block, effect, (org.bukkit.entity.Player) entityhuman.getBukkitEntity(), isPrimary); ++ if (CraftEventFactory.callEvent(event).isCancelled()) continue; ++ entityhuman.addEffect(new MobEffectInstance(CraftPotionUtil.fromBukkit(event.getEffect())), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON); ++ // Paper end - BeaconEffectEvent + } ++ } ++ } + +- if (beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null) { +- iterator = list.iterator(); +- +- while (iterator.hasNext()) { +- entityhuman = (Player) iterator.next(); +- entityhuman.addEffect(new MobEffectInstance(secondaryEffect, j, 0, true, true)); +- } ++ private static boolean hasSecondaryEffect(int i, @Nullable Holder holder, @Nullable Holder holder1) { ++ { ++ if (i >= 4 && !Objects.equals(holder, holder1) && holder1 != null) { ++ return true; + } + ++ return false; + } + } + ++ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect) { ++ // Paper start - Custom beacon ranges ++ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); ++ } ++ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges ++ if (!world.isClientSide && primaryEffect != null) { ++ double d0 = (double) (beaconLevel * 10 + 10); ++ byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); ++ ++ int j = BeaconBlockEntity.getLevel(beaconLevel); ++ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges ++ ++ BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent ++ ++ if (BeaconBlockEntity.hasSecondaryEffect(beaconLevel, primaryEffect, secondaryEffect)) { ++ BeaconBlockEntity.applyEffect(list, secondaryEffect, j, 0, false, pos); // Paper - BeaconEffectEvent ++ } ++ } ++ ++ } ++ // CraftBukkit end ++ + public static void playSound(Level world, BlockPos pos, SoundEvent sound) { + world.playSound((Player) null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F); + } +@@ -316,7 +440,7 @@ + if (nbt.contains(key, 8)) { + ResourceLocation minecraftkey = ResourceLocation.tryParse(nbt.getString(key)); + +- return minecraftkey == null ? null : (Holder) BuiltInRegistries.MOB_EFFECT.get(minecraftkey).map(BeaconBlockEntity::filterEffect).orElse((Object) null); ++ return minecraftkey == null ? null : (Holder) BuiltInRegistries.MOB_EFFECT.get(minecraftkey).orElse(null); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598) + } else { + return null; + } +@@ -327,11 +451,13 @@ + super.loadAdditional(nbt, registries); + this.primaryPower = BeaconBlockEntity.loadEffect(nbt, "primary_effect"); + this.secondaryPower = BeaconBlockEntity.loadEffect(nbt, "secondary_effect"); ++ this.levels = nbt.getInt("Levels"); // CraftBukkit - SPIGOT-5053, use where available + if (nbt.contains("CustomName", 8)) { + this.name = parseCustomNameSafe(nbt.getString("CustomName"), registries); + } + + this.lockKey = LockCode.fromTag(nbt, registries); ++ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges + } + + @Override +@@ -345,6 +471,7 @@ + } + + this.lockKey.addToTag(nbt, registries); ++ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges + } + + public void setCustomName(@Nullable Component customName) { +@@ -360,7 +487,7 @@ + @Nullable + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch new file mode 100644 index 0000000000..8457946183 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch @@ -0,0 +1,306 @@ +--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -43,6 +43,10 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end ++ + public class BeehiveBlockEntity extends BlockEntity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -56,6 +60,7 @@ + private List stored = Lists.newArrayList(); + @Nullable + public BlockPos savedFlowerPos; ++ public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold + + public BeehiveBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.BEEHIVE, pos, state); +@@ -95,7 +100,7 @@ + } + + public boolean isFull() { +- return this.stored.size() == 3; ++ return this.stored.size() == this.maxBees; // CraftBukkit + } + + public void emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus beeState) { +@@ -112,7 +117,7 @@ + + if (player.position().distanceToSqr(entity.position()) <= 16.0D) { + if (!this.isSedated()) { +- entitybee.setTarget(player); ++ entitybee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit + } else { + entitybee.setStayOutOfHiveCountdown(400); + } +@@ -124,10 +129,16 @@ + } + + private List releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus beeState) { ++ // CraftBukkit start - This allows us to bypass the night/rain/emergency check ++ return this.releaseBees(state, beeState, false); ++ } ++ ++ public List releaseBees(BlockState iblockdata, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + + this.stored.removeIf((tileentitybeehive_hivebee) -> { +- return BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, state, tileentitybeehive_hivebee.toOccupant(), list, beeState, this.savedFlowerPos); ++ return BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, tileentitybeehive_hivebee.toOccupant(), list, tileentitybeehive_releasestatus, this.savedFlowerPos, force); ++ // CraftBukkit end + }); + if (!list.isEmpty()) { + super.setChanged(); +@@ -141,6 +152,11 @@ + return this.stored.size(); + } + ++ // Paper start - Add EntityBlockStorage clearEntities ++ public void clearBees() { ++ this.stored.clear(); ++ } ++ // Paper end - Add EntityBlockStorage clearEntities + public static int getHoneyLevel(BlockState state) { + return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); + } +@@ -151,7 +167,17 @@ + } + + public void addOccupant(Bee entity) { +- if (this.stored.size() < 3) { ++ if (this.stored.size() < this.maxBees) { // CraftBukkit ++ // CraftBukkit start ++ if (this.level != null) { ++ org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.getBlockPos())); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ entity.setStayOutOfHiveCountdown(400); ++ return; ++ } ++ } ++ // CraftBukkit end + entity.stopRiding(); + entity.ejectPassengers(); + entity.dropLeash(); +@@ -167,7 +193,7 @@ + this.level.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, this.getBlockState())); + } + +- entity.discard(); ++ entity.discard(EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause + super.setChanged(); + } + } +@@ -177,32 +203,50 @@ + } + + private static boolean releaseOccupant(Level world, BlockPos pos, BlockState state, BeehiveBlockEntity.Occupant bee, @Nullable List entities, BeehiveBlockEntity.BeeReleaseStatus beeState, @Nullable BlockPos flowerPos) { +- if (Bee.isNightOrRaining(world) && beeState != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { ++ // CraftBukkit start - This allows us to bypass the night/rain/emergency check ++ return BeehiveBlockEntity.releaseOccupant(world, pos, state, bee, entities, beeState, flowerPos, false); ++ } ++ ++ private static boolean releaseOccupant(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.Occupant tileentitybeehive_c, @Nullable List list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) { ++ if (!force && Bee.isNightOrRaining(world) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { ++ // CraftBukkit end + return false; + } else { +- Direction enumdirection = (Direction) state.getValue(BeehiveBlock.FACING); +- BlockPos blockposition2 = pos.relative(enumdirection); ++ Direction enumdirection = (Direction) iblockdata.getValue(BeehiveBlock.FACING); ++ BlockPos blockposition2 = blockposition.relative(enumdirection); + boolean flag = !world.getBlockState(blockposition2).getCollisionShape(world, blockposition2).isEmpty(); + +- if (flag && beeState != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { ++ if (flag && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { + return false; + } else { +- Entity entity = bee.createEntity(world, pos); ++ Entity entity = tileentitybeehive_c.createEntity(world, blockposition); + + if (entity != null) { ++ // CraftBukkit start + if (entity instanceof Bee) { ++ float f = entity.getBbWidth(); ++ double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F); ++ double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX(); ++ double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F); ++ double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ(); ++ ++ entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot()); ++ } ++ if (!world.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BEEHIVE)) return false; // CraftBukkit - SpawnReason, moved from below ++ // CraftBukkit end ++ if (entity instanceof Bee) { + Bee entitybee = (Bee) entity; + +- if (flowerPos != null && !entitybee.hasSavedFlowerPos() && world.random.nextFloat() < 0.9F) { +- entitybee.setSavedFlowerPos(flowerPos); ++ if (blockposition1 != null && !entitybee.hasSavedFlowerPos() && world.random.nextFloat() < 0.9F) { ++ entitybee.setSavedFlowerPos(blockposition1); + } + +- if (beeState == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) { ++ if (tileentitybeehive_releasestatus == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) { + entitybee.dropOffNectar(); +- if (state.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> { ++ if (iblockdata.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> { + return blockbase_blockdata.hasProperty(BeehiveBlock.HONEY_LEVEL); + })) { +- int i = BeehiveBlockEntity.getHoneyLevel(state); ++ int i = BeehiveBlockEntity.getHoneyLevel(iblockdata); + + if (i < 5) { + int j = world.random.nextInt(100) == 0 ? 2 : 1; +@@ -211,27 +255,35 @@ + --j; + } + +- world.setBlockAndUpdate(pos, (BlockState) state.setValue(BeehiveBlock.HONEY_LEVEL, i + j)); ++ // Paper start - Fire EntityChangeBlockEvent in more places ++ BlockState newBlockState = iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j); ++ ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entitybee, blockposition, newBlockState)) { ++ world.setBlockAndUpdate(blockposition, newBlockState); ++ } ++ // Paper end - Fire EntityChangeBlockEvent in more places + } + } + } + +- if (entities != null) { +- entities.add(entitybee); ++ if (list != null) { ++ list.add(entitybee); + } + ++ /* // CraftBukkit start + float f = entity.getBbWidth(); + double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F); +- double d1 = (double) pos.getX() + 0.5D + d0 * (double) enumdirection.getStepX(); +- double d2 = (double) pos.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F); +- double d3 = (double) pos.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ(); ++ double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX(); ++ double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F); ++ double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ(); + + entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot()); ++ */ // CraftBukkit end + } + +- world.playSound((Player) null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, world.getBlockState(pos))); +- return world.addFreshEntity(entity); ++ world.playSound((Player) null, blockposition, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F); ++ world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, world.getBlockState(blockposition))); ++ return true; // return this.world.addFreshEntity(entity); // CraftBukkit - moved up + } else { + return false; + } +@@ -256,6 +308,10 @@ + if (BeehiveBlockEntity.releaseOccupant(world, pos, state, tileentitybeehive_hivebee.toOccupant(), (List) null, tileentitybeehive_releasestatus, flowerPos)) { + flag = true; + iterator.remove(); ++ // CraftBukkit start ++ } else { ++ tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - Fix bees aging inside hives; use exitTickCounter to keep actual bee life ++ // CraftBukkit end + } + } + } +@@ -282,7 +338,7 @@ + @Override + protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.loadAdditional(nbt, registries); +- this.stored.clear(); ++ this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change) + if (nbt.contains("bees")) { + BeehiveBlockEntity.Occupant.LIST_CODEC.parse(NbtOps.INSTANCE, nbt.get("bees")).resultOrPartial((s) -> { + BeehiveBlockEntity.LOGGER.error("Failed to parse bees: '{}'", s); +@@ -291,7 +347,12 @@ + }); + } + +- this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse((Object) null); ++ this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse(null); // CraftBukkit - decompile error ++ // CraftBukkit start ++ if (nbt.contains("Bukkit.MaxEntities")) { ++ this.maxBees = nbt.getInt("Bukkit.MaxEntities"); ++ } ++ // CraftBukkit end + } + + @Override +@@ -301,13 +362,14 @@ + if (this.hasSavedFlowerPos()) { + nbt.put("flower_pos", NbtUtils.writeBlockPos(this.savedFlowerPos)); + } ++ nbt.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit + + } + + @Override + protected void applyImplicitComponents(BlockEntity.DataComponentInput components) { + super.applyImplicitComponents(components); +- this.stored.clear(); ++ this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change) + List list = (List) components.getOrDefault(DataComponents.BEES, List.of()); + + list.forEach(this::storeBee); +@@ -348,7 +410,7 @@ + CompoundTag nbttagcompound = new CompoundTag(); + + entity.save(nbttagcompound); +- List list = BeehiveBlockEntity.IGNORED_BEE_TAGS; ++ List list = BeehiveBlockEntity.IGNORED_BEE_TAGS; // CraftBukkit - decompile error + + Objects.requireNonNull(nbttagcompound); + list.forEach(nbttagcompound::remove); +@@ -367,7 +429,7 @@ + @Nullable + public Entity createEntity(Level world, BlockPos pos) { + CompoundTag nbttagcompound = this.entityData.copyTag(); +- List list = BeehiveBlockEntity.IGNORED_BEE_TAGS; ++ List list = BeehiveBlockEntity.IGNORED_BEE_TAGS; // CraftBukkit - decompile error + + Objects.requireNonNull(nbttagcompound); + list.forEach(nbttagcompound::remove); +@@ -391,6 +453,7 @@ + } + + private static void setBeeReleaseData(int ticksInHive, Bee beeEntity) { ++ if (!beeEntity.ageLocked) { // Paper - Honor ageLock + int j = beeEntity.getAge(); + + if (j < 0) { +@@ -400,21 +463,25 @@ + } + + beeEntity.setInLoveTime(Math.max(0, beeEntity.getInLoveTime() - ticksInHive)); ++ } // Paper - Honor ageLock + } + } + + private static class BeeData { + + private final BeehiveBlockEntity.Occupant occupant; ++ private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts + private int ticksInHive; + + BeeData(BeehiveBlockEntity.Occupant data) { + this.occupant = data; + this.ticksInHive = data.ticksInHive(); ++ this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives + } + + public boolean tick() { +- return this.ticksInHive++ > this.occupant.minTicksInHive; ++ this.ticksInHive++; // Paper - Fix bees aging inside hives ++ return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives + } + + public BeehiveBlockEntity.Occupant toOccupant() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch new file mode 100644 index 0000000000..c8f5ced60a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch @@ -0,0 +1,65 @@ +--- a/net/minecraft/world/level/block/entity/BellBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BellBlockEntity.java +@@ -63,6 +63,11 @@ + + if (blockEntity.ticks >= 50) { + blockEntity.shaking = false; ++ // Paper start - Fix bell block entity memory leak ++ if (!blockEntity.resonating) { ++ blockEntity.nearbyEntities.clear(); ++ } ++ // Paper end - Fix bell block entity memory leak + blockEntity.ticks = 0; + } + +@@ -76,6 +81,7 @@ + ++blockEntity.resonationTicks; + } else { + bellEffect.run(world, pos, blockEntity.nearbyEntities); ++ blockEntity.nearbyEntities.clear(); // Paper - Fix bell block entity memory leak + blockEntity.resonating = false; + } + } +@@ -120,11 +126,12 @@ + LivingEntity entityliving = (LivingEntity) iterator.next(); + + if (entityliving.isAlive() && !entityliving.isRemoved() && blockposition.closerToCenterThan(entityliving.position(), 32.0D)) { +- entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, (Object) this.level.getGameTime()); ++ entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, this.level.getGameTime()); // CraftBukkit - decompile error + } + } + } + ++ this.nearbyEntities.removeIf(e -> !e.isAlive()); // Paper - Fix bell block entity memory leak + } + + private static boolean areRaidersNearby(BlockPos pos, List hearingEntities) { +@@ -144,9 +151,13 @@ + } + + private static void makeRaidersGlow(Level world, BlockPos pos, List hearingEntities) { ++ List entities = // CraftBukkit + hearingEntities.stream().filter((entityliving) -> { + return BellBlockEntity.isRaiderWithinRange(pos, entityliving); +- }).forEach(BellBlockEntity::glow); ++ }).map((entity) -> (org.bukkit.entity.LivingEntity) entity.getBukkitEntity()).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new)); // CraftBukkit ++ ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(world, pos, entities).forEach(entity -> glow(entity, pos)); // Paper - Add BellRevealRaiderEvent ++ // CraftBukkit end + } + + private static void showBellParticles(Level world, BlockPos pos, List hearingEntities) { +@@ -178,6 +189,13 @@ + } + + private static void glow(LivingEntity entity) { ++ // Paper start - Add BellRevealRaiderEvent ++ glow(entity, null); ++ } ++ ++ private static void glow(LivingEntity entity, @javax.annotation.Nullable BlockPos pos) { ++ if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), pos), (org.bukkit.entity.Raider) entity.getBukkitEntity()).callEvent()) return; ++ // Paper end - Add BellRevealRaiderEvent + entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch new file mode 100644 index 0000000000..aca157f8aa --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -0,0 +1,153 @@ +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -26,8 +26,18 @@ + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end ++ + public abstract class BlockEntity { + ++ // CraftBukkit start - data containers ++ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); ++ public CraftPersistentDataContainer persistentDataContainer; ++ // CraftBukkit end + private static final Logger LOGGER = LogUtils.getLogger(); + private final BlockEntityType type; + @Nullable +@@ -43,6 +53,7 @@ + this.worldPosition = pos.immutable(); + this.validateBlockState(state); + this.blockState = state; ++ this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init + } + + private void validateBlockState(BlockState state) { +@@ -74,7 +85,16 @@ + return this.level != null; + } + +- protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {} ++ // CraftBukkit start - read container ++ protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { ++ this.persistentDataContainer.clear(); // Paper - clear instead of init ++ ++ net.minecraft.nbt.Tag persistentDataTag = nbt.get("PublicBukkitValues"); ++ if (persistentDataTag instanceof CompoundTag) { ++ this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); ++ } ++ } ++ // CraftBukkit end + + public final void loadWithComponents(CompoundTag nbt, HolderLookup.Provider registries) { + this.loadAdditional(nbt, registries); +@@ -114,6 +134,11 @@ + }).ifPresent((nbtbase) -> { + nbttagcompound.merge((CompoundTag) nbtbase); + }); ++ // CraftBukkit start - store container ++ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) { ++ nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound()); ++ } ++ // CraftBukkit end + return nbttagcompound; + } + +@@ -121,6 +146,11 @@ + CompoundTag nbttagcompound = new CompoundTag(); + + this.saveAdditional(nbttagcompound, registries); ++ // Paper start - store PDC here as well ++ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) { ++ nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound()); ++ } ++ // Paper end + return nbttagcompound; + } + +@@ -234,7 +264,12 @@ + public void fillCrashReportCategory(CrashReportCategory crashReportSection) { + crashReportSection.setDetail("Name", this::getNameForReporting); + if (this.level != null) { +- CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.getBlockState()); ++ // Paper start - Prevent block entity and entity crashes ++ BlockState block = this.getBlockState(); ++ if (block != null) { ++ CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, block); ++ } ++ // Paper end - Prevent block entity and entity crashes + CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition)); + } + } +@@ -263,13 +298,19 @@ + } + + public final void applyComponents(DataComponentMap defaultComponents, DataComponentPatch components) { ++ // CraftBukkit start ++ this.applyComponentsSet(defaultComponents, components); ++ } ++ ++ public final Set> applyComponentsSet(DataComponentMap datacomponentmap, DataComponentPatch datacomponentpatch) { ++ // CraftBukkit end + final Set> set = new HashSet(); + + set.add(DataComponents.BLOCK_ENTITY_DATA); + set.add(DataComponents.BLOCK_STATE); +- final PatchedDataComponentMap patcheddatacomponentmap = PatchedDataComponentMap.fromPatch(defaultComponents, components); ++ final PatchedDataComponentMap patcheddatacomponentmap = PatchedDataComponentMap.fromPatch(datacomponentmap, datacomponentpatch); + +- this.applyImplicitComponents(new BlockEntity.DataComponentInput(this) { ++ this.applyImplicitComponents(new BlockEntity.DataComponentInput() { // CraftBukkit - decompile error + @Nullable + @Override + public T get(DataComponentType type) { +@@ -284,9 +325,13 @@ + } + }); + Objects.requireNonNull(set); +- DataComponentPatch datacomponentpatch1 = components.forget(set::contains); ++ DataComponentPatch datacomponentpatch1 = datacomponentpatch.forget(set::contains); + + this.components = datacomponentpatch1.split().added(); ++ // CraftBukkit start ++ set.remove(DataComponents.BLOCK_ENTITY_DATA); // Remove as never actually added by applyImplicitComponents ++ return set; ++ // CraftBukkit end + } + + protected void collectImplicitComponents(DataComponentMap.Builder builder) {} +@@ -321,6 +366,30 @@ + } + } + ++ // CraftBukkit start - add method ++ public InventoryHolder getOwner() { ++ // Paper start ++ return getOwner(true); ++ } ++ public InventoryHolder getOwner(boolean useSnapshot) { ++ // Paper end ++ if (this.level == null) return null; ++ org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists ++ org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper ++ if (state instanceof InventoryHolder) return (InventoryHolder) state; ++ return null; ++ } ++ // CraftBukkit end ++ ++ // Paper start - Sanitize sent data ++ public CompoundTag sanitizeSentNbt(CompoundTag tag) { ++ tag.remove("PublicBukkitValues"); ++ ++ return tag; ++ } ++ // Paper end - Sanitize sent data ++ + private static class ComponentHelper { + + public static final Codec COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec(); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntityType.java.patch new file mode 100644 index 0000000000..c2e5be1463 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntityType.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/block/entity/BlockEntityType.java ++++ b/net/minecraft/world/level/block/entity/BlockEntityType.java +@@ -66,7 +66,7 @@ + public static final BlockEntityType CRAFTER = BlockEntityType.register("crafter", CrafterBlockEntity::new, Blocks.CRAFTER); + public static final BlockEntityType TRIAL_SPAWNER = BlockEntityType.register("trial_spawner", TrialSpawnerBlockEntity::new, Blocks.TRIAL_SPAWNER); + public static final BlockEntityType VAULT = BlockEntityType.register("vault", VaultBlockEntity::new, Blocks.VAULT); +- private static final Set> OP_ONLY_CUSTOM_DATA = Set.of(BlockEntityType.COMMAND_BLOCK, BlockEntityType.LECTERN, BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN, BlockEntityType.MOB_SPAWNER, BlockEntityType.TRIAL_SPAWNER); ++ private static final Set> OP_ONLY_CUSTOM_DATA = Set.of(BlockEntityType.COMMAND_BLOCK, BlockEntityType.LECTERN, BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN, BlockEntityType.MOB_SPAWNER, BlockEntityType.TRIAL_SPAWNER); // CraftBukkit // Paper - Allow chests to be placed with NBT data + private final BlockEntityType.BlockEntitySupplier factory; + public final Set validBlocks; + private final Holder.Reference> builtInRegistryHolder; +@@ -110,7 +110,7 @@ + public T getBlockEntity(BlockGetter world, BlockPos pos) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- return tileentity != null && tileentity.getType() == this ? tileentity : null; ++ return tileentity != null && tileentity.getType() == this ? (T) tileentity : null; // CraftBukkit - decompile error + } + + public boolean onlyOpCanSetNbt() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch new file mode 100644 index 0000000000..39dec9ecc2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch @@ -0,0 +1,232 @@ +--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -8,7 +8,6 @@ + import net.minecraft.core.NonNullList; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; +-import net.minecraft.tags.ItemTags; + import net.minecraft.world.ContainerHelper; + import net.minecraft.world.Containers; + import net.minecraft.world.WorldlyContainer; +@@ -23,6 +22,20 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.BrewingStandBlock; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.List; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.tags.ItemTags; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.block.BrewingStartEvent; ++import org.bukkit.event.inventory.BrewEvent; ++import org.bukkit.event.inventory.BrewingStandFuelEvent; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end + + public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer { + +@@ -37,11 +50,42 @@ + public static final int NUM_DATA_VALUES = 2; + private NonNullList items; + public int brewTime; ++ public int recipeBrewTime = 400; // Paper - Add recipeBrewTime + private boolean[] lastPotionCount; + private Item ingredient; + public int fuel; + protected final ContainerData dataAccess; ++ // CraftBukkit start - add fields and methods ++ // private int lastTick = MinecraftServer.currentTick; // Paper - remove anti tick skipping measures / wall time ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; + ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + public BrewingStandBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.BREWING_STAND, pos, state); + this.items = NonNullList.withSize(5, ItemStack.EMPTY); +@@ -57,6 +101,11 @@ + case 1: + j = BrewingStandBlockEntity.this.fuel; + break; ++ // Paper start - Add recipeBrewTime ++ case 2: ++ j = BrewingStandBlockEntity.this.recipeBrewTime; ++ break; ++ // Paper end - Add recipeBrewTime + default: + j = 0; + } +@@ -72,13 +121,18 @@ + break; + case 1: + BrewingStandBlockEntity.this.fuel = value; ++ // Paper start - Add recipeBrewTime ++ case 2: ++ BrewingStandBlockEntity.this.recipeBrewTime = value; ++ break; ++ // Paper end - Add recipeBrewTime + } + + } + + @Override + public int getCount() { +- return 2; ++ return 3; // Paper - Add recipeBrewTime + } + }; + } +@@ -107,8 +161,19 @@ + ItemStack itemstack = (ItemStack) blockEntity.items.get(4); + + if (blockEntity.fuel <= 0 && itemstack.is(ItemTags.BREWING_FUEL)) { +- blockEntity.fuel = 20; +- itemstack.shrink(1); ++ // CraftBukkit start ++ BrewingStandFuelEvent event = new BrewingStandFuelEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack), 20); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ blockEntity.fuel = event.getFuelPower(); ++ if (blockEntity.fuel > 0 && event.isConsuming()) { ++ itemstack.shrink(1); ++ } ++ // CraftBukkit end + setChanged(world, pos, state); + } + +@@ -116,12 +181,15 @@ + boolean flag1 = blockEntity.brewTime > 0; + ItemStack itemstack1 = (ItemStack) blockEntity.items.get(3); + ++ // Paper - remove anti tick skipping measures / wall time ++ + if (flag1) { +- --blockEntity.brewTime; +- boolean flag2 = blockEntity.brewTime == 0; ++ --blockEntity.brewTime; // Paper - remove anti tick skipping measures / wall time - revert to vanilla ++ boolean flag2 = blockEntity.brewTime <= 0; // == -> <= ++ // CraftBukkit end + + if (flag2 && flag) { +- BrewingStandBlockEntity.doBrew(world, pos, blockEntity.items); ++ BrewingStandBlockEntity.doBrew(world, pos, blockEntity.items, blockEntity); // CraftBukkit + } else if (!flag || !itemstack1.is(blockEntity.ingredient)) { + blockEntity.brewTime = 0; + } +@@ -129,7 +197,12 @@ + setChanged(world, pos, state); + } else if (flag && blockEntity.fuel > 0) { + --blockEntity.fuel; +- blockEntity.brewTime = 400; ++ // CraftBukkit start ++ BrewingStartEvent event = new BrewingStartEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack1), 400); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ blockEntity.recipeBrewTime = event.getRecipeBrewTime(); // Paper - use recipe brew time from event ++ blockEntity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event ++ // CraftBukkit end + blockEntity.ingredient = itemstack1.getItem(); + setChanged(world, pos, state); + } +@@ -185,14 +258,36 @@ + } + } + +- private static void doBrew(Level world, BlockPos pos, NonNullList slots) { +- ItemStack itemstack = (ItemStack) slots.get(3); ++ private static void doBrew(Level world, BlockPos blockposition, NonNullList nonnulllist, BrewingStandBlockEntity tileentitybrewingstand) { // CraftBukkit ++ ItemStack itemstack = (ItemStack) nonnulllist.get(3); + PotionBrewing potionbrewer = world.potionBrewing(); + ++ // CraftBukkit start ++ InventoryHolder owner = tileentitybrewingstand.getOwner(); ++ List brewResults = new ArrayList<>(3); + for (int i = 0; i < 3; ++i) { +- slots.set(i, potionbrewer.mix(itemstack, (ItemStack) slots.get(i))); ++ brewResults.add(i, CraftItemStack.asCraftMirror(potionbrewer.mix(itemstack, (ItemStack) nonnulllist.get(i)))); + } + ++ if (owner != null) { ++ BrewEvent event = new BrewEvent(CraftBlock.at(world, blockposition), (org.bukkit.inventory.BrewerInventory) owner.getInventory(), brewResults, tileentitybrewingstand.fuel); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end ++ ++ for (int i = 0; i < 3; ++i) { ++ // CraftBukkit start - validate index in case it is cleared by plugins ++ if (i < brewResults.size()) { ++ nonnulllist.set(i, CraftItemStack.asNMSCopy(brewResults.get(i))); ++ } else { ++ nonnulllist.set(i, ItemStack.EMPTY); ++ } ++ // CraftBukkit end ++ } ++ + itemstack.shrink(1); + ItemStack itemstack1 = itemstack.getItem().getCraftingRemainder(); + +@@ -200,12 +295,12 @@ + if (itemstack.isEmpty()) { + itemstack = itemstack1; + } else { +- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); ++ Containers.dropItemStack(world, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack1); + } + } + +- slots.set(3, itemstack); +- world.levelEvent(1035, pos, 0); ++ nonnulllist.set(3, itemstack); ++ world.levelEvent(1035, blockposition, 0); + } + + @Override +@@ -231,12 +326,12 @@ + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { ++ PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up + if (slot == 3) { +- PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; + + return potionbrewer.isIngredient(stack); + } else { +- return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty(); ++ return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch new file mode 100644 index 0000000000..57515e3834 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch @@ -0,0 +1,36 @@ +--- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java +@@ -31,6 +31,12 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.Arrays; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++// CraftBukkit end ++ + public class BrushableBlockEntity extends BlockEntity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -151,7 +157,10 @@ + ItemEntity entityitem = new ItemEntity(world, d3, d4, d5, this.item.split(world.random.nextInt(21) + 10)); + + entityitem.setDeltaMovement(Vec3.ZERO); +- world.addFreshEntity(entityitem); ++ // CraftBukkit start ++ org.bukkit.block.Block bblock = CraftBlock.at(this.level, this.worldPosition); ++ CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), (ServerPlayer) player, Arrays.asList(entityitem)); ++ // CraftBukkit end + this.item = ItemStack.EMPTY; + } + +@@ -185,7 +194,7 @@ + + private boolean tryLoadLootTable(CompoundTag nbt) { + if (nbt.contains("LootTable", 8)) { +- this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))); ++ this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation + this.lootTableSeed = nbt.getLong("LootTableSeed"); + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch new file mode 100644 index 0000000000..944379decd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java +@@ -20,6 +20,12 @@ + public VibrationSystem.User createVibrationUser() { + return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos()); + } ++ // Paper start - Configurable sculk sensor listener range ++ @Override ++ protected void saveRangeOverride(final net.minecraft.nbt.CompoundTag nbt) { ++ if (this.rangeOverride != null && this.rangeOverride != 16) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default ++ } ++ // Paper end - Configurable sculk sensor listener range + + protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser { + public VibrationUser(final BlockPos pos) { +@@ -28,6 +34,7 @@ + + @Override + public int getListenerRadius() { ++ if (CalibratedSculkSensorBlockEntity.this.rangeOverride != null) return CalibratedSculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range + return 16; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch new file mode 100644 index 0000000000..aea8cb9be4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch @@ -0,0 +1,121 @@ +--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -31,6 +31,14 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.gameevent.GameEvent; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockCookEvent; ++import org.bukkit.event.block.CampfireStartEvent; ++import org.bukkit.inventory.CampfireRecipe; ++// CraftBukkit end ++ + public class CampfireBlockEntity extends BlockEntity implements Clearable { + + private static final int BURN_COOL_SPEED = 2; +@@ -38,12 +46,14 @@ + private final NonNullList items; + public final int[] cookingProgress; + public final int[] cookingTime; ++ public final boolean[] stopCooking; // Paper - Add more Campfire API + + public CampfireBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.CAMPFIRE, pos, state); + this.items = NonNullList.withSize(4, ItemStack.EMPTY); + this.cookingProgress = new int[4]; + this.cookingTime = new int[4]; ++ this.stopCooking = new boolean[4]; // Paper - Add more Campfire API + } + + public static void cookTick(ServerLevel world, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity, RecipeManager.CachedCheck recipeMatchGetter) { +@@ -54,16 +64,42 @@ + + if (!itemstack.isEmpty()) { + flag = true; ++ if (!blockEntity.stopCooking[i]) { // Paper - Add more Campfire API + int j = blockEntity.cookingProgress[i]++; ++ } // Paper - Add more Campfire API + + if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) { + SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack); +- ItemStack itemstack1 = (ItemStack) recipeMatchGetter.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { ++ // Paper start - add recipe to cook events ++ final Optional> recipeHolderOptional = recipeMatchGetter.getRecipeFor(singlerecipeinput, world); ++ ItemStack itemstack1 = (ItemStack) recipeHolderOptional.map((recipeholder) -> { ++ // Paper end - add recipe to cook events + return ((CampfireCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, world.registryAccess()); + }).orElse(itemstack); + + if (itemstack1.isItemEnabled(world.enabledFeatures())) { +- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); ++ // CraftBukkit start - fire BlockCookEvent ++ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); ++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); ++ ++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events ++ world.getCraftServer().getPluginManager().callEvent(blockCookEvent); ++ ++ if (blockCookEvent.isCancelled()) { ++ return; ++ } ++ ++ result = blockCookEvent.getResult(); ++ itemstack1 = CraftItemStack.asNMSCopy(result); ++ // CraftBukkit end ++ // Paper start - Fix item locations dropped from campfires ++ double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR; ++ while (!itemstack1.isEmpty()) { ++ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10)); ++ droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation)); ++ world.addFreshEntity(droppedItem); ++ } ++ // Paper end - Fix item locations dropped from campfires + blockEntity.items.set(i, ItemStack.EMPTY); + world.sendBlockUpdated(pos, state, state, 3); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); +@@ -143,6 +179,16 @@ + System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length)); + } + ++ // Paper start - Add more Campfire API ++ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) { ++ byte[] abyte = nbt.getByteArray("Paper.StopCooking"); ++ boolean[] cookingState = new boolean[4]; ++ for (int index = 0; index < abyte.length; index++) { ++ cookingState[index] = abyte[index] == 1; ++ } ++ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length)); ++ } ++ // Paper end - Add more Campfire API + } + + @Override +@@ -151,6 +197,13 @@ + ContainerHelper.saveAllItems(nbt, this.items, true, registries); + nbt.putIntArray("CookingTimes", this.cookingProgress); + nbt.putIntArray("CookingTotalTimes", this.cookingTime); ++ // Paper start - Add more Campfire API ++ byte[] cookingState = new byte[4]; ++ for (int index = 0; index < cookingState.length; index++) { ++ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0); ++ } ++ nbt.putByteArray("Paper.StopCooking", cookingState); ++ // Paper end - Add more Campfire API + } + + @Override +@@ -177,7 +230,11 @@ + return false; + } + +- this.cookingTime[i] = ((CampfireCookingRecipe) ((RecipeHolder) optional.get()).value()).cookingTime(); ++ // CraftBukkit start ++ CampfireStartEvent event = new CampfireStartEvent(CraftBlock.at(this.level,this.worldPosition), CraftItemStack.asCraftMirror(stack), (CampfireRecipe) optional.get().toBukkitRecipe()); ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ this.cookingTime[i] = event.getTotalCookTime(); // i -> event.getTotalCookTime() ++ // CraftBukkit end + this.cookingProgress[i] = 0; + this.items.set(i, stack.consumeAndReturn(1, entity)); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState())); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch new file mode 100644 index 0000000000..537f1913b2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -23,6 +23,11 @@ + import net.minecraft.world.level.block.ChestBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.ChestType; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity { + +@@ -31,6 +36,36 @@ + public final ContainerOpenersCounter openersCounter; + private final ChestLidController chestLidController; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + protected ChestBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + this.items = NonNullList.withSize(27, ItemStack.EMPTY); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch new file mode 100644 index 0000000000..af35923253 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch @@ -0,0 +1,85 @@ +--- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java +@@ -23,13 +23,55 @@ + import net.minecraft.world.level.gameevent.GameEvent; + import org.slf4j.Logger; + ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class ChiseledBookShelfBlockEntity extends BlockEntity implements Container { + + public static final int MAX_BOOKS_IN_STORAGE = 6; + private static final Logger LOGGER = LogUtils.getLogger(); + private final NonNullList items; + public int lastInteractedSlot; ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList<>(); ++ private int maxStack = 1; + ++ @Override ++ public List getContents() { ++ return this.items; ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ if (this.level == null) return null; ++ return new org.bukkit.Location(this.level.getWorld(), this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ } ++ // CraftBukkit end ++ + public ChiseledBookShelfBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.CHISELED_BOOKSHELF, pos, state); + this.items = NonNullList.withSize(6, ItemStack.EMPTY); +@@ -100,7 +142,7 @@ + + this.items.set(slot, ItemStack.EMPTY); + if (!itemstack.isEmpty()) { +- this.updateState(slot); ++ if (this.level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world + } + + return itemstack; +@@ -115,7 +157,7 @@ + public void setItem(int slot, ItemStack stack) { + if (stack.is(ItemTags.BOOKSHELF_BOOKS)) { + this.items.set(slot, stack); +- this.updateState(slot); ++ if (this.level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world + } else if (stack.isEmpty()) { + this.removeItem(slot, 1); + } +@@ -131,7 +173,7 @@ + + @Override + public int getMaxStackSize() { +- return 1; ++ return this.maxStack; // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch new file mode 100644 index 0000000000..49a6bff68c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java +@@ -24,7 +24,14 @@ + private boolean auto; + private boolean conditionMet; + private final BaseCommandBlock commandBlock = new BaseCommandBlock() { ++ // CraftBukkit start + @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, CommandBlockEntity.this); ++ } ++ // CraftBukkit end ++ ++ @Override + public void setCommand(String command) { + super.setCommand(command); + CommandBlockEntity.this.setChanged(); +@@ -51,7 +58,7 @@ + public CommandSourceStack createCommandSourceStack() { + Direction enumdirection = (Direction) CommandBlockEntity.this.getBlockState().getValue(CommandBlock.FACING); + +- return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), 2, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); ++ return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); // Paper - configurable command block perm level + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch new file mode 100644 index 0000000000..6dd6c8627e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch @@ -0,0 +1,108 @@ +--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +@@ -10,6 +10,7 @@ + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; +@@ -187,11 +188,23 @@ + } + + private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { +- int i = activatingBlocks.size(); ++ // CraftBukkit start ++ ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks)); ++ } ++ ++ public static int getRange(List list) { ++ // CraftBukkit end ++ int i = list.size(); + int j = i / 7 * 16; +- int k = pos.getX(); +- int l = pos.getY(); +- int i1 = pos.getZ(); ++ // CraftBukkit start ++ return j; ++ } ++ ++ private static void applyEffects(Level world, BlockPos blockposition, int j) { // j = effect range in blocks ++ // CraftBukkit end ++ int k = blockposition.getX(); ++ int l = blockposition.getY(); ++ int i1 = blockposition.getZ(); + AABB axisalignedbb = (new AABB((double) k, (double) l, (double) i1, (double) (k + 1), (double) (l + 1), (double) (i1 + 1))).inflate((double) j).expandTowards(0.0D, (double) world.getHeight(), 0.0D); + List list1 = world.getEntitiesOfClass(Player.class, axisalignedbb); + +@@ -201,8 +214,8 @@ + while (iterator.hasNext()) { + Player entityhuman = (Player) iterator.next(); + +- if (pos.closerThan(entityhuman.blockPosition(), (double) j) && entityhuman.isInWaterOrRain()) { +- entityhuman.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true)); ++ if (blockposition.closerThan(entityhuman.blockPosition(), (double) j) && entityhuman.isInWaterOrRain()) { ++ entityhuman.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONDUIT); // CraftBukkit + } + } + +@@ -210,33 +223,42 @@ + } + + private static void updateDestroyTarget(Level world, BlockPos pos, BlockState state, List activatingBlocks, ConduitBlockEntity blockEntity) { +- LivingEntity entityliving = blockEntity.destroyTarget; +- int i = activatingBlocks.size(); ++ // CraftBukkit start - add "damageTarget" boolean ++ ConduitBlockEntity.updateDestroyTarget(world, pos, state, activatingBlocks, blockEntity, true); ++ } + ++ public static void updateDestroyTarget(Level world, BlockPos blockposition, BlockState iblockdata, List list, ConduitBlockEntity tileentityconduit, boolean damageTarget) { ++ // CraftBukkit end ++ LivingEntity entityliving = tileentityconduit.destroyTarget; ++ int i = list.size(); ++ + if (i < 42) { +- blockEntity.destroyTarget = null; +- } else if (blockEntity.destroyTarget == null && blockEntity.destroyTargetUUID != null) { +- blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID); +- blockEntity.destroyTargetUUID = null; +- } else if (blockEntity.destroyTarget == null) { +- List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> { ++ tileentityconduit.destroyTarget = null; ++ } else if (tileentityconduit.destroyTarget == null && tileentityconduit.destroyTargetUUID != null) { ++ tileentityconduit.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, blockposition, tileentityconduit.destroyTargetUUID); ++ tileentityconduit.destroyTargetUUID = null; ++ } else if (tileentityconduit.destroyTarget == null) { ++ List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition), (entityliving1) -> { + return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); + }); + + if (!list1.isEmpty()) { +- blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); ++ tileentityconduit.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); + } +- } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) { +- blockEntity.destroyTarget = null; ++ } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), 8.0D)) { ++ tileentityconduit.destroyTarget = null; + } + +- if (blockEntity.destroyTarget != null) { +- world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); +- blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F); ++ // CraftBukkit start ++ if (damageTarget && tileentityconduit.destroyTarget != null) { ++ if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), 4.0F)) { ++ world.playSound(null, tileentityconduit.destroyTarget.getX(), tileentityconduit.destroyTarget.getY(), tileentityconduit.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); ++ } ++ // CraftBukkit end + } + +- if (entityliving != blockEntity.destroyTarget) { +- world.sendBlockUpdated(pos, state, state, 2); ++ if (entityliving != tileentityconduit.destroyTarget) { ++ world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 2); + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch new file mode 100644 index 0000000000..b594786d46 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch @@ -0,0 +1,76 @@ +--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java ++++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java +@@ -17,6 +17,7 @@ + private static final int CHECK_TICK_DELAY = 5; + private int openCount; + private double maxInteractionRange; ++ public boolean opened; // CraftBukkit + + public ContainerOpenersCounter() {} + +@@ -26,11 +27,36 @@ + + protected abstract void openerCountChanged(Level world, BlockPos pos, BlockState state, int oldViewerCount, int newViewerCount); + ++ // CraftBukkit start ++ public void onAPIOpen(Level world, BlockPos blockposition, BlockState iblockdata) { ++ this.onOpen(world, blockposition, iblockdata); ++ } ++ ++ public void onAPIClose(Level world, BlockPos blockposition, BlockState iblockdata) { ++ this.onClose(world, blockposition, iblockdata); ++ } ++ ++ public void openerAPICountChanged(Level world, BlockPos blockposition, BlockState iblockdata, int i, int j) { ++ this.openerCountChanged(world, blockposition, iblockdata, i, j); ++ } ++ // CraftBukkit end ++ + protected abstract boolean isOwnContainer(Player player); + + public void incrementOpeners(Player player, Level world, BlockPos pos, BlockState state) { ++ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added + int i = this.openCount++; + ++ // CraftBukkit start - Call redstone event ++ if (world.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) { ++ int newPower = Math.max(0, Math.min(15, this.openCount)); ++ ++ if (oldPower != newPower) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, oldPower, newPower); ++ } ++ } ++ // CraftBukkit end ++ + if (i == 0) { + this.onOpen(world, pos, state); + world.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_OPEN, pos); +@@ -42,8 +68,20 @@ + } + + public void decrementOpeners(Player player, Level world, BlockPos pos, BlockState state) { ++ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added ++ if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative + int i = this.openCount--; + ++ // CraftBukkit start - Call redstone event ++ if (world.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) { ++ int newPower = Math.max(0, Math.min(15, this.openCount)); ++ ++ if (oldPower != newPower) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, oldPower, newPower); ++ } ++ } ++ // CraftBukkit end ++ + if (this.openCount == 0) { + this.onClose(world, pos, state); + world.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_CLOSE, pos); +@@ -72,6 +110,7 @@ + } + + int i = list.size(); ++ if (this.opened) i++; // CraftBukkit - add dummy count from API + int j = this.openCount; + + if (j != i) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch new file mode 100644 index 0000000000..e71ac50ca3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch @@ -0,0 +1,68 @@ +--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java +@@ -22,6 +22,11 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.CrafterBlock; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer { + +@@ -35,12 +40,52 @@ + private NonNullList items; + public int craftingTicksRemaining; + protected final ContainerData containerData; ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList<>(); ++ private int maxStack = MAX_STACK; + ++ @Override ++ public List getContents() { ++ return this.items; ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ if (this.level == null) return null; ++ return new org.bukkit.Location(this.level.getWorld(), this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ } ++ // CraftBukkit end ++ + public CrafterBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.CRAFTER, pos, state); + this.items = NonNullList.withSize(9, ItemStack.EMPTY); + this.craftingTicksRemaining = 0; +- this.containerData = new ContainerData(this) { ++ this.containerData = new ContainerData() { // CraftBukkit - decompile error + private final int[] slotStates = new int[9]; + private int triggered = 0; + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch new file mode 100644 index 0000000000..6c93436b46 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch @@ -0,0 +1,62 @@ +--- a/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java +@@ -20,8 +20,59 @@ + import net.minecraft.world.level.storage.loot.LootTable; + import net.minecraft.world.ticks.ContainerSingleItem; + ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class DecoratedPotBlockEntity extends BlockEntity implements RandomizableContainer, ContainerSingleItem.BlockContainerSingleItem { + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new ArrayList<>(); ++ private int maxStack = MAX_STACK; ++ ++ @Override ++ public List getContents() { ++ return Arrays.asList(this.item); ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ @Override ++ public void setMaxStackSize(int i) { ++ this.maxStack = i; ++ } ++ ++ @Override ++ public Location getLocation() { ++ if (this.level == null) return null; ++ return CraftLocation.toBukkit(this.worldPosition, this.level.getWorld()); ++ } ++ // CraftBukkit end ++ + public static final String TAG_SHERDS = "sherds"; + public static final String TAG_ITEM = "item"; + public static final int EVENT_POT_WOBBLES = 1; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch new file mode 100644 index 0000000000..ae1688f953 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch @@ -0,0 +1,50 @@ +--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +@@ -13,12 +13,47 @@ + import net.minecraft.world.inventory.DispenserMenu; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class DispenserBlockEntity extends RandomizableContainerBlockEntity { + + public static final int CONTAINER_SIZE = 9; + private NonNullList items; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + protected DispenserBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + this.items = NonNullList.withSize(9, ItemStack.EMPTY); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch new file mode 100644 index 0000000000..58e1ea39e6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch @@ -0,0 +1,322 @@ +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -11,6 +11,7 @@ + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.tags.BlockTags; ++import net.minecraft.world.CompoundContainer; + import net.minecraft.world.Container; + import net.minecraft.world.ContainerHelper; + import net.minecraft.world.WorldlyContainer; +@@ -18,7 +19,6 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.EntitySelector; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.player.Inventory; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.HopperMenu; +@@ -29,6 +29,18 @@ + import net.minecraft.world.level.block.HopperBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftInventory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.inventory.HopperInventorySearchEvent; ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.event.inventory.InventoryPickupItemEvent; ++import org.bukkit.inventory.Inventory; ++// CraftBukkit end + + public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper { + +@@ -40,6 +52,36 @@ + private long tickedGameTime; + private Direction facing; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ ++ public List getContents() { ++ return this.items; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + public HopperBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.HOPPER, pos, state); + this.items = NonNullList.withSize(5, ItemStack.EMPTY); +@@ -102,9 +144,14 @@ + blockEntity.tickedGameTime = world.getGameTime(); + if (!blockEntity.isOnCooldown()) { + blockEntity.setCooldown(0); +- HopperBlockEntity.tryMoveItems(world, pos, state, blockEntity, () -> { ++ // Spigot start ++ boolean result = HopperBlockEntity.tryMoveItems(world, pos, state, blockEntity, () -> { + return HopperBlockEntity.suckInItems(world, blockEntity); + }); ++ if (!result && blockEntity.level.spigotConfig.hopperCheck > 1) { ++ blockEntity.setCooldown(blockEntity.level.spigotConfig.hopperCheck); ++ } ++ // Spigot end + } + + } +@@ -125,7 +172,7 @@ + } + + if (flag) { +- blockEntity.setCooldown(8); ++ blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Spigot + setChanged(world, pos, state); + return true; + } +@@ -167,15 +214,41 @@ + + if (!itemstack.isEmpty()) { + int j = itemstack.getCount(); +- ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, blockEntity.removeItem(i, 1), enumdirection); ++ // CraftBukkit start - Call event when pushing items into other inventories ++ ItemStack original = itemstack.copy(); ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot + ++ Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else if (iinventory.getOwner() != null) { ++ destinationInventory = iinventory.getOwner().getInventory(); ++ } else { ++ destinationInventory = new CraftInventory(iinventory); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ blockEntity.setItem(i, original); ++ blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); ++ // CraftBukkit end ++ + if (itemstack1.isEmpty()) { + iinventory.setChanged(); + return true; + } + + itemstack.setCount(j); +- if (j == 1) { ++ // Spigot start ++ itemstack.shrink(origCount - itemstack1.getCount()); ++ if (j <= world.spigotConfig.hopperAmount) { ++ // Spigot end + blockEntity.setItem(i, itemstack); + } + } +@@ -249,7 +322,7 @@ + for (int j = 0; j < i; ++j) { + int k = aint[j]; + +- if (HopperBlockEntity.tryTakeInItemFromSlot(hopper, iinventory, k, enumdirection)) { ++ if (HopperBlockEntity.tryTakeInItemFromSlot(hopper, iinventory, k, enumdirection, world)) { // Spigot + return true; + } + } +@@ -274,21 +347,52 @@ + } + } + +- private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) { +- ItemStack itemstack = inventory.getItem(slot); ++ private static boolean tryTakeInItemFromSlot(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot ++ ItemStack itemstack = iinventory.getItem(i); + +- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(hopper, inventory, itemstack, slot, side)) { ++ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { + int j = itemstack.getCount(); +- ItemStack itemstack1 = HopperBlockEntity.addItem(inventory, hopper, inventory.removeItem(slot, 1), (Direction) null); ++ // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ ItemStack original = itemstack.copy(); ++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot + ++ Inventory sourceInventory; ++ // Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else if (iinventory.getOwner() != null) { ++ sourceInventory = iinventory.getOwner().getInventory(); ++ } else { ++ sourceInventory = new CraftInventory(iinventory); ++ } ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false); ++ ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ iinventory.setItem(i, original); ++ ++ if (ihopper instanceof HopperBlockEntity) { ++ ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot ++ } ++ ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ // CraftBukkit end ++ + if (itemstack1.isEmpty()) { +- inventory.setChanged(); ++ iinventory.setChanged(); + return true; + } + + itemstack.setCount(j); +- if (j == 1) { +- inventory.setItem(slot, itemstack); ++ // Spigot start ++ itemstack.shrink(origCount - itemstack1.getCount()); ++ if (j <= world.spigotConfig.hopperAmount) { ++ // Spigot end ++ iinventory.setItem(i, itemstack); + } + } + +@@ -297,13 +401,20 @@ + + public static boolean addItem(Container inventory, ItemEntity itemEntity) { + boolean flag = false; ++ // CraftBukkit start ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); ++ itemEntity.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + ItemStack itemstack = itemEntity.getItem().copy(); + ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null); + + if (itemstack1.isEmpty()) { + flag = true; + itemEntity.setItem(ItemStack.EMPTY); +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } else { + itemEntity.setItem(itemstack1); + } +@@ -383,11 +494,18 @@ + boolean flag1 = to.isEmpty(); + + if (itemstack1.isEmpty()) { ++ // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem ++ ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size ++ if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) { ++ leftover = stack; // Paper - Make hoppers respect inventory max stack size ++ stack = stack.split(to.getMaxStackSize()); ++ } ++ // Spigot end + to.setItem(slot, stack); +- stack = ItemStack.EMPTY; ++ stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +- int j = stack.getMaxStackSize() - itemstack1.getCount(); ++ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - Make hoppers respect inventory max stack size + int k = Math.min(stack.getCount(), j); + + stack.shrink(k); +@@ -410,7 +528,7 @@ + } + } + +- tileentityhopper.setCooldown(8 - b0); ++ tileentityhopper.setCooldown(tileentityhopper.level.spigotConfig.hopperTransfer - b0); // Spigot + } + } + +@@ -421,14 +539,38 @@ + return stack; + } + ++ // CraftBukkit start + @Nullable ++ private static Container runHopperInventorySearchEvent(Container inventory, CraftBlock hopper, CraftBlock searchLocation, HopperInventorySearchEvent.ContainerType containerType) { ++ HopperInventorySearchEvent event = new HopperInventorySearchEvent((inventory != null) ? new CraftInventory(inventory) : null, containerType, hopper, searchLocation); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ CraftInventory craftInventory = (CraftInventory) event.getInventory(); ++ return (craftInventory != null) ? craftInventory.getInventory() : null; ++ } ++ // CraftBukkit end ++ ++ @Nullable + private static Container getAttachedContainer(Level world, BlockPos pos, HopperBlockEntity blockEntity) { +- return HopperBlockEntity.getContainerAt(world, pos.relative(blockEntity.facing)); ++ // CraftBukkit start ++ BlockPos searchPosition = pos.relative(blockEntity.facing); ++ Container inventory = HopperBlockEntity.getContainerAt(world, searchPosition); ++ ++ CraftBlock hopper = CraftBlock.at(world, pos); ++ CraftBlock searchBlock = CraftBlock.at(world, searchPosition); ++ return HopperBlockEntity.runHopperInventorySearchEvent(inventory, hopper, searchBlock, HopperInventorySearchEvent.ContainerType.DESTINATION); ++ // CraftBukkit end + } + + @Nullable + private static Container getSourceContainer(Level world, Hopper hopper, BlockPos pos, BlockState state) { +- return HopperBlockEntity.getContainerAt(world, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ()); ++ // CraftBukkit start ++ Container inventory = HopperBlockEntity.getContainerAt(world, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ()); ++ ++ BlockPos blockPosition = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY(), hopper.getLevelZ()); ++ CraftBlock hopper1 = CraftBlock.at(world, blockPosition); ++ CraftBlock container = CraftBlock.at(world, blockPosition.above()); ++ return HopperBlockEntity.runHopperInventorySearchEvent(inventory, hopper1, container, HopperInventorySearchEvent.ContainerType.SOURCE); ++ // CraftBukkit end + } + + public static List getItemsAtAndAbove(Level world, Hopper hopper) { +@@ -455,6 +597,7 @@ + + @Nullable + private static Container getBlockContainer(Level world, BlockPos pos, BlockState state) { ++ if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( pos ) ) return null; // Spigot + Block block = state.getBlock(); + + if (block instanceof WorldlyContainerHolder) { +@@ -543,7 +686,7 @@ + } + + @Override +- protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { ++ protected AbstractContainerMenu createMenu(int syncId, net.minecraft.world.entity.player.Inventory playerInventory) { + return new HopperMenu(syncId, playerInventory, this); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch new file mode 100644 index 0000000000..11519f80c2 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/entity/JigsawBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/JigsawBlockEntity.java +@@ -131,7 +131,12 @@ + public void generate(ServerLevel world, int maxDepth, boolean keepJigsaws) { + BlockPos blockPos = this.getBlockPos().relative(this.getBlockState().getValue(JigsawBlock.ORIENTATION).front()); + Registry registry = world.registryAccess().lookupOrThrow(Registries.TEMPLATE_POOL); +- Holder holder = registry.getOrThrow(this.pool); ++ // Paper start - Replace getHolderOrThrow with a null check ++ Holder holder = registry.get(this.pool).orElse(null); ++ if (holder == null) { ++ return; ++ } ++ // Paper end - Replace getHolderOrThrow with a null check + JigsawPlacement.generateJigsaw(world, holder, this.target, maxDepth, blockPos, keepJigsaws); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch new file mode 100644 index 0000000000..fa8fa48ae9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch @@ -0,0 +1,92 @@ +--- a/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java +@@ -19,13 +19,57 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.ticks.ContainerSingleItem; + ++// CraftBukkit start ++import java.util.Collections; ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end ++ + public class JukeboxBlockEntity extends BlockEntity implements ContainerSingleItem.BlockContainerSingleItem { + + public static final String SONG_ITEM_TAG_ID = "RecordItem"; + public static final String TICKS_SINCE_SONG_STARTED_TAG_ID = "ticks_since_song_started"; + private ItemStack item; + private final JukeboxSongPlayer jukeboxSongPlayer; ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ public boolean opened; + ++ @Override ++ public List getContents() { ++ return Collections.singletonList(this.item); ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ ++ @Override ++ public Location getLocation() { ++ if (this.level == null) return null; ++ return new org.bukkit.Location(this.level.getWorld(), this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); ++ } ++ // CraftBukkit end ++ + public JukeboxBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.JUKEBOX, pos, state); + this.item = ItemStack.EMPTY; +@@ -137,7 +181,7 @@ + + @Override + public int getMaxStackSize() { +- return 1; ++ return this.maxStack; // CraftBukkit + } + + @Override +@@ -156,12 +200,17 @@ + } + + @VisibleForTesting +- public void setSongItemWithoutPlaying(ItemStack stack) { +- this.item = stack; +- JukeboxSong.fromStack(this.level.registryAccess(), stack).ifPresent((holder) -> { +- this.jukeboxSongPlayer.setSongWithoutPlaying(holder, 0L); ++ public void setSongItemWithoutPlaying(ItemStack itemstack, long ticksSinceSongStarted) { // CraftBukkit - add argument ++ this.item = itemstack; ++ this.jukeboxSongPlayer.song = null; // CraftBukkit - reset ++ JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), itemstack).ifPresent((holder) -> { // Paper - fallback to other RegistyrAccess if no level ++ this.jukeboxSongPlayer.setSongWithoutPlaying(holder, ticksSinceSongStarted); // CraftBukkit - add argument + }); +- this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock()); ++ // CraftBukkit start - add null check for level ++ if (this.level != null) { ++ this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock()); ++ } ++ // CraftBukkit end + this.setChanged(); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch new file mode 100644 index 0000000000..afaf4c6781 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch @@ -0,0 +1,164 @@ +--- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java +@@ -28,6 +28,17 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import org.bukkit.Location; ++import org.bukkit.block.Lectern; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.inventory.InventoryHolder; ++// CraftBukkit end + + public class LecternBlockEntity extends BlockEntity implements Clearable, MenuProvider { + +@@ -35,8 +46,55 @@ + public static final int NUM_DATA = 1; + public static final int SLOT_BOOK = 0; + public static final int NUM_SLOTS = 1; +- public final Container bookAccess = new Container() { ++ // CraftBukkit start - add fields and methods ++ public final Container bookAccess = new LecternInventory(); ++ public class LecternInventory implements Container { ++ ++ public List transaction = new ArrayList<>(); ++ private int maxStack = 1; ++ + @Override ++ public List getContents() { ++ return Arrays.asList(LecternBlockEntity.this.book); ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public void setMaxStackSize(int i) { ++ this.maxStack = i; ++ } ++ ++ @Override ++ public Location getLocation() { ++ if (LecternBlockEntity.this.level == null) return null; ++ return CraftLocation.toBukkit(LecternBlockEntity.this.worldPosition, LecternBlockEntity.this.level.getWorld()); ++ } ++ ++ @Override ++ public InventoryHolder getOwner() { ++ return (Lectern) LecternBlockEntity.this.getOwner(); ++ } ++ ++ public LecternBlockEntity getLectern() { ++ return LecternBlockEntity.this; ++ } ++ // CraftBukkit end ++ ++ @Override + public int getContainerSize() { + return 1; + } +@@ -80,11 +138,20 @@ + } + + @Override +- public void setItem(int slot, ItemStack stack) {} ++ // CraftBukkit start ++ public void setItem(int slot, ItemStack stack) { ++ if (slot == 0) { ++ LecternBlockEntity.this.setBook(stack); ++ if (LecternBlockEntity.this.getLevel() != null) { ++ LecternBlock.resetBookState(null, LecternBlockEntity.this.getLevel(), LecternBlockEntity.this.getBlockPos(), LecternBlockEntity.this.getBlockState(), LecternBlockEntity.this.hasBook()); ++ } ++ } ++ } ++ // CraftBukkit end + + @Override + public int getMaxStackSize() { +- return 1; ++ return this.maxStack; // CraftBukkit + } + + @Override +@@ -164,7 +231,7 @@ + if (j != this.page) { + this.page = j; + this.setChanged(); +- LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); ++ if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit + } + + } +@@ -189,6 +256,35 @@ + return book; + } + ++ // CraftBukkit start ++ private final CommandSource commandSource = new CommandSource() { ++ ++ @Override ++ public void sendSystemMessage(Component message) { ++ } ++ ++ @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, LecternBlockEntity.this); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return false; ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return false; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return false; ++ } ++ }; ++ // CraftBukkit end ++ + private CommandSourceStack createCommandSourceStack(@Nullable Player player, ServerLevel world) { + String s; + Object object; +@@ -203,7 +299,8 @@ + + Vec3 vec3d = Vec3.atCenterOf(this.worldPosition); + +- return new CommandSourceStack(CommandSource.NULL, vec3d, Vec2.ZERO, world, 2, s, (Component) object, world.getServer(), player); ++ // CraftBukkit - commandSource ++ return new CommandSourceStack(this.commandSource, vec3d, Vec2.ZERO, world, 2, s, (Component) object, world.getServer(), player); + } + + @Override +@@ -236,7 +333,7 @@ + + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- return new LecternMenu(syncId, this.bookAccess, this.dataAccess); ++ return new LecternMenu(syncId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch new file mode 100644 index 0000000000..16ff81b3cd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -115,4 +115,13 @@ + nbt.remove("LootTable"); + nbt.remove("LootTableSeed"); + } ++ ++ // Paper start - LootTable API ++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper ++ ++ @Override ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ return this.lootableData; ++ } ++ // Paper end - LootTable API + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch new file mode 100644 index 0000000000..49d4011082 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -37,8 +37,18 @@ + this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(state, new BlockPositionSource(pos)); + } + ++ // Paper start - Fix NPE in SculkBloomEvent world access ++ @Override ++ public void setLevel(Level level) { ++ super.setLevel(level); ++ this.catalystListener.sculkSpreader.level = level; ++ } ++ // Paper end - Fix NPE in SculkBloomEvent world access ++ + public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. + blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true); ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit + } + + @Override +@@ -69,6 +79,7 @@ + this.blockState = state; + this.positionSource = positionSource; + this.sculkSpreader = SculkSpreader.createLevelSpreader(); ++ // this.sculkSpreader.level = this.level; // CraftBukkit // Paper - Fix NPE in SculkBloomEvent world access + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch new file mode 100644 index 0000000000..112cb33190 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch @@ -0,0 +1,49 @@ +--- a/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +@@ -26,6 +26,7 @@ + private final VibrationSystem.Listener vibrationListener; + private final VibrationSystem.User vibrationUser = this.createVibrationUser(); + public int lastVibrationFrequency; ++ @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range + + protected SculkSensorBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); +@@ -52,8 +53,16 @@ + .resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string)) + .ifPresent(listener -> this.vibrationData = listener); + } ++ // Paper start - Configurable sculk sensor listener range ++ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) { ++ this.rangeOverride = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); ++ } else { ++ this.rangeOverride = null; ++ } ++ // Paper end - Configurable sculk sensor listener range + } + ++ protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range + @Override + protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.saveAdditional(nbt, registries); +@@ -63,7 +72,13 @@ + .encodeStart(registryOps, this.vibrationData) + .resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string)) + .ifPresent(listenerNbt -> nbt.put("listener", listenerNbt)); ++ this.saveRangeOverride(nbt); // Paper - Configurable sculk sensor listener range + } ++ // Paper start - Configurable sculk sensor listener range ++ protected void saveRangeOverride(CompoundTag nbt) { ++ if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default ++ } ++ // Paper end - Configurable sculk sensor listener range + + @Override + public VibrationSystem.Data getVibrationData() { +@@ -100,6 +115,7 @@ + + @Override + public int getListenerRadius() { ++ if (SculkSensorBlockEntity.this.rangeOverride != null) return SculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range + return 8; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch new file mode 100644 index 0000000000..fb8ab267d1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +@@ -105,6 +105,13 @@ + + @Nullable + public static ServerPlayer tryGetPlayer(@Nullable Entity entity) { ++ // Paper start - check global player list where appropriate; ensure level is the same for sculk events ++ final ServerPlayer player = tryGetPlayer0(entity); ++ return player != null && player.level() == entity.level() ? player : null; ++ } ++ @Nullable ++ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) { ++ // Paper end - check global player list where appropriate + if (entity instanceof ServerPlayer) { + return (ServerPlayer)entity; + } else { +@@ -190,7 +197,7 @@ + private boolean trySummonWarden(ServerLevel world) { + return this.warningLevel >= 4 + && SpawnUtil.trySpawnMob( +- EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false ++ EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null // Paper - Entity#getEntitySpawnReason + ) + .isPresent(); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch new file mode 100644 index 0000000000..384ab32533 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch @@ -0,0 +1,67 @@ +--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +@@ -33,6 +33,10 @@ + import net.minecraft.world.level.material.PushReaction; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.entity.HumanEntity; ++// CraftBukkit end + + public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer { + +@@ -52,6 +56,37 @@ + @Nullable + private final DyeColor color; + ++ // CraftBukkit start - add fields and methods ++ public List transaction = new java.util.ArrayList(); ++ private int maxStack = MAX_STACK; ++ public boolean opened; ++ ++ public List getContents() { ++ return this.itemStacks; ++ } ++ ++ public void onOpen(CraftHumanEntity who) { ++ this.transaction.add(who); ++ } ++ ++ public void onClose(CraftHumanEntity who) { ++ this.transaction.remove(who); ++ } ++ ++ public List getViewers() { ++ return this.transaction; ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.maxStack; ++ } ++ ++ public void setMaxStackSize(int size) { ++ this.maxStack = size; ++ } ++ // CraftBukkit end ++ + public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState state) { + super(BlockEntityType.SHULKER_BOX, pos, state); + this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); +@@ -184,6 +219,7 @@ + } + + ++this.openCount; ++ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call. + this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); + if (this.openCount == 1) { + this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_OPEN, this.worldPosition); +@@ -197,6 +233,7 @@ + public void stopOpen(Player player) { + if (!this.remove && !player.isSpectator()) { + --this.openCount; ++ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call. + this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); + if (this.openCount <= 0) { + this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_CLOSE, this.worldPosition); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch new file mode 100644 index 0000000000..788235f327 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch @@ -0,0 +1,269 @@ +--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -22,12 +22,12 @@ + import net.minecraft.network.chat.Style; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.network.FilteredText; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; +-import net.minecraft.world.entity.player.Player; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.SignBlock; +@@ -35,6 +35,12 @@ + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; ++import org.bukkit.block.sign.Side; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.entity.Player; ++import org.bukkit.event.block.SignChangeEvent; ++// CraftBukkit end + + public class SignBlockEntity extends BlockEntity { + +@@ -61,13 +67,18 @@ + return new SignText(); + } + +- public boolean isFacingFrontText(Player player) { ++ public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) { ++ // Paper start - More Sign Block API ++ return this.isFacingFrontText(player.getX(), player.getZ()); ++ } ++ public boolean isFacingFrontText(double x, double z) { ++ // Paper end - More Sign Block API + Block block = this.getBlockState().getBlock(); + + if (block instanceof SignBlock blocksign) { + Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState()); +- double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x); +- double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z); ++ double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - More Sign Block API ++ double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - More Sign Block API + float f = blocksign.getYRotationDegrees(this.getBlockState()); + float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F; + +@@ -101,7 +112,7 @@ + protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.saveAdditional(nbt, registries); + DynamicOps dynamicops = registries.createSerializationContext(NbtOps.INSTANCE); +- DataResult dataresult = SignText.DIRECT_CODEC.encodeStart(dynamicops, this.frontText); ++ DataResult dataresult = SignText.DIRECT_CODEC.encodeStart(dynamicops, this.frontText); // CraftBukkit - decompile error + Logger logger = SignBlockEntity.LOGGER; + + Objects.requireNonNull(logger); +@@ -121,7 +132,7 @@ + protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.loadAdditional(nbt, registries); + DynamicOps dynamicops = registries.createSerializationContext(NbtOps.INSTANCE); +- DataResult dataresult; ++ DataResult dataresult; // CraftBukkit - decompile error + Logger logger; + + if (nbt.contains("front_text")) { +@@ -161,7 +172,7 @@ + + if (world instanceof ServerLevel worldserver) { + try { +- return ComponentUtils.updateForEntity(SignBlockEntity.createCommandSourceStack((Player) null, worldserver, this.worldPosition), text, (Entity) null, 0); ++ return ComponentUtils.updateForEntity(this.createCommandSourceStack((net.minecraft.world.entity.player.Player) null, worldserver, this.worldPosition), text, (Entity) null, 0); + } catch (CommandSyntaxException commandsyntaxexception) { + ; + } +@@ -170,15 +181,17 @@ + return text; + } + +- public void updateSignText(Player player, boolean front, List messages) { ++ public void updateSignText(net.minecraft.world.entity.player.Player player, boolean front, List messages) { + if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) { + this.updateText((signtext) -> { +- return this.setMessages(player, messages, signtext); ++ return this.setMessages(player, messages, signtext, front); // CraftBukkit + }, front); + this.setAllowedPlayerEditor((UUID) null); + this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); + } else { + SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); ++ if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update ++ ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit + } + } + +@@ -188,19 +201,43 @@ + return this.setText((SignText) textChanger.apply(signtext), front); + } + +- private SignText setMessages(Player player, List messages, SignText text) { +- for (int i = 0; i < messages.size(); ++i) { +- FilteredText filteredtext = (FilteredText) messages.get(i); +- Style chatmodifier = text.getMessage(i, player.isTextFilteringEnabled()).getStyle(); ++ private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List list, SignText signtext, boolean front) { // CraftBukkit ++ SignText originalText = signtext; // CraftBukkit ++ for (int i = 0; i < list.size(); ++i) { ++ FilteredText filteredtext = (FilteredText) list.get(i); ++ Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); + +- if (player.isTextFilteringEnabled()) { +- text = text.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); ++ if (entityhuman.isTextFilteringEnabled()) { ++ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + } else { +- text = text.setMessage(i, Component.literal(filteredtext.raw()).setStyle(chatmodifier), Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); ++ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + } + } + +- return text; ++ // CraftBukkit start ++ Player player = ((ServerPlayer) entityhuman).getBukkitEntity(); ++ List lines = new java.util.ArrayList<>(); // Paper - adventure ++ ++ for (int i = 0; i < list.size(); ++i) { ++ lines.add(io.papermc.paper.adventure.PaperAdventure.asAdventure(signtext.getMessage(i, entityhuman.isTextFilteringEnabled()))); // Paper - Adventure ++ } ++ ++ SignChangeEvent event = new SignChangeEvent(CraftBlock.at(this.level, this.worldPosition), player, new java.util.ArrayList<>(lines), (front) ? Side.FRONT : Side.BACK); // Paper - Adventure ++ entityhuman.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return originalText; ++ } ++ ++ Component[] components = org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.lines()); // Paper - Adventure ++ for (int i = 0; i < components.length; i++) { ++ if (!Objects.equals(lines.get(i), event.line(i))) { // Paper - Adventure ++ signtext = signtext.setMessage(i, components[i]); ++ } ++ } ++ // CraftBukkit end ++ ++ return signtext; + } + + public boolean setText(SignText text, boolean front) { +@@ -227,11 +264,11 @@ + } + } + +- public boolean canExecuteClickCommands(boolean front, Player player) { ++ public boolean canExecuteClickCommands(boolean front, net.minecraft.world.entity.player.Player player) { + return this.isWaxed() && this.getText(front).hasAnyClickCommands(player); + } + +- public boolean executeClickCommandsIfPresent(Player player, Level world, BlockPos pos, boolean front) { ++ public boolean executeClickCommandsIfPresent(net.minecraft.world.entity.player.Player player, Level world, BlockPos pos, boolean front) { + boolean flag1 = false; + Component[] aichatbasecomponent = this.getText(front).getMessages(player.isTextFilteringEnabled()); + int i = aichatbasecomponent.length; +@@ -242,7 +279,17 @@ + ClickEvent chatclickable = chatmodifier.getClickEvent(); + + if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) { +- player.getServer().getCommands().performPrefixedCommand(SignBlockEntity.createCommandSourceStack(player, world, pos), chatclickable.getValue()); ++ // Paper start - Fix commands from signs not firing command events ++ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue(); ++ if (org.spigotmc.SpigotConfig.logCommands) { ++ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command); ++ } ++ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent((org.bukkit.entity.Player) player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) CraftBlock.at(this.level, this.worldPosition).getState(), front ? Side.FRONT : Side.BACK); ++ if (!event.callEvent()) { ++ return false; ++ } ++ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), world, pos), event.getMessage()); ++ // Paper end - Fix commands from signs not firing command events + flag1 = true; + } + } +@@ -250,11 +297,55 @@ + return flag1; + } + +- private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level world, BlockPos pos) { ++ // CraftBukkit start ++ private final CommandSource commandSource = new CommandSource() { ++ ++ @Override ++ public void sendSystemMessage(Component message) {} ++ ++ @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, SignBlockEntity.this); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return false; ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return false; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return false; ++ } ++ }; ++ ++ private CommandSourceStack createCommandSourceStack(@Nullable net.minecraft.world.entity.player.Player player, Level world, BlockPos pos) { ++ // CraftBukkit end + String s = player == null ? "Sign" : player.getName().getString(); + Object object = player == null ? Component.literal("Sign") : player.getDisplayName(); + +- return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); ++ // Paper start - Fix commands from signs not firing command events ++ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) { ++ @Override ++ public void sendSystemMessage(Component message) { ++ if (player instanceof final ServerPlayer serverPlayer) { ++ serverPlayer.sendSystemMessage(message); ++ } ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ } : this.commandSource; ++ // Paper end - Fix commands from signs not firing command events ++ // CraftBukkit - this ++ return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events + } + + @Override +@@ -273,12 +364,17 @@ + + @Nullable + public UUID getPlayerWhoMayEdit() { ++ // CraftBukkit start - unnecessary sign ticking removed, so do this lazily ++ if (this.level != null && this.playerWhoMayEdit != null) { ++ this.clearInvalidPlayerWhoMayEdit(this, this.level, this.playerWhoMayEdit); ++ } ++ // CraftBukkit end + return this.playerWhoMayEdit; + } + + private void markUpdated() { + this.setChanged(); +- this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); ++ if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122) + } + + public boolean isWaxed() { +@@ -296,7 +392,7 @@ + } + + public boolean playerIsTooFarAwayToEdit(UUID uuid) { +- Player entityhuman = this.level.getPlayerByUUID(uuid); ++ net.minecraft.world.entity.player.Player entityhuman = this.level.getPlayerByUUID(uuid); + + return entityhuman == null || !entityhuman.canInteractWithBlock(this.getBlockPos(), 4.0D); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch new file mode 100644 index 0000000000..a683a1cb1b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch @@ -0,0 +1,73 @@ +--- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java +@@ -41,7 +41,7 @@ + @Nullable + private static LoadingCache>> profileCacheByName; + @Nullable +- private static LoadingCache>> profileCacheById; ++ private static LoadingCache, CompletableFuture>> profileCacheById; // Paper - player profile events + public static final Executor CHECKED_MAIN_THREAD_EXECUTOR = runnable -> { + Executor executor = mainThreadExecutor; + if (executor != null) { +@@ -76,9 +76,9 @@ + profileCacheById = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofMinutes(10L)) + .maximumSize(256L) +- .build(new CacheLoader>>() { ++ .build(new CacheLoader<>() { // Paper - player profile events + @Override +- public CompletableFuture> load(UUID uUID) { ++ public CompletableFuture> load(com.mojang.datafixers.util.Pair uUID) { // Paper - player profile events + return SkullBlockEntity.fetchProfileById(uUID, apiServices, booleanSupplier); + } + }); +@@ -89,23 +89,29 @@ + .getAsync(name) + .thenCompose( + optional -> { +- LoadingCache>> loadingCache = profileCacheById; ++ LoadingCache, CompletableFuture>> loadingCache = profileCacheById; // Paper - player profile events + return loadingCache != null && !optional.isEmpty() +- ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional2 -> optional2.or(() -> optional)) ++ ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional2 -> optional2.or(() -> optional)) // Paper - player profile events + : CompletableFuture.completedFuture(Optional.empty()); + } + ); + } + +- static CompletableFuture> fetchProfileById(UUID uuid, Services apiServices, BooleanSupplier booleanSupplier) { ++ static CompletableFuture> fetchProfileById(com.mojang.datafixers.util.Pair pair, Services apiServices, BooleanSupplier booleanSupplier) { // Paper + return CompletableFuture.supplyAsync(() -> { + if (booleanSupplier.getAsBoolean()) { + return Optional.empty(); + } else { +- ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true); ++ // Paper start - fill player profile events ++ if (apiServices.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) { ++ final GameProfile profile = pair.getSecond() != null ? pair.getSecond() : new com.mojang.authlib.GameProfile(pair.getFirst(), ""); ++ return Optional.ofNullable(paperService.fetchProfile(profile, true)).map(ProfileResult::profile); ++ } ++ ProfileResult profileResult = apiServices.sessionService().fetchProfile(pair.getFirst(), true); ++ // Paper end - fill player profile events + return Optional.ofNullable(profileResult).map(ProfileResult::profile); + } +- }, Util.backgroundExecutor().forName("fetchProfile")); ++ }, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + } + + public static void clear() { +@@ -210,9 +216,11 @@ + : CompletableFuture.completedFuture(Optional.empty()); + } + +- public static CompletableFuture> fetchGameProfile(UUID uuid) { +- LoadingCache>> loadingCache = profileCacheById; +- return loadingCache != null ? loadingCache.getUnchecked(uuid) : CompletableFuture.completedFuture(Optional.empty()); ++ // Paper start - player profile events ++ public static CompletableFuture> fetchGameProfile(UUID uuid, @Nullable String name) { ++ LoadingCache, CompletableFuture>> loadingCache = profileCacheById; ++ return loadingCache != null ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(uuid, name != null ? new com.mojang.authlib.GameProfile(uuid, name) : null)) : CompletableFuture.completedFuture(Optional.empty()); ++ // Paper end - player profile events + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch new file mode 100644 index 0000000000..b5587ac5d9 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -21,6 +21,7 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; + import net.minecraft.world.level.levelgen.feature.Feature; + import net.minecraft.world.level.levelgen.feature.configurations.EndGatewayConfiguration; +@@ -143,7 +144,7 @@ + public Vec3 getPortalPosition(ServerLevel world, BlockPos pos) { + BlockPos blockposition1; + +- if (this.exitPortal == null && world.dimension() == Level.END) { ++ if (this.exitPortal == null && world.getTypeKey() == LevelStem.END) { // CraftBukkit - work in alternate worlds + blockposition1 = TheEndGatewayBlockEntity.findOrCreateValidTeleportPos(world, pos); + blockposition1 = blockposition1.above(10); + TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", blockposition1); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch new file mode 100644 index 0000000000..e58a4bb1dc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch @@ -0,0 +1,63 @@ +--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java ++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java +@@ -46,6 +46,11 @@ + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.CollisionContext; ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseLootEvent; ++// CraftBukkit end + + public final class TrialSpawner { + +@@ -219,14 +224,21 @@ + } + + entityinsentient.setPersistenceRequired(); +- Optional optional1 = mobspawnerdata.getEquipment(); ++ Optional optional1 = mobspawnerdata.getEquipment(); // CraftBukkit - decompile error + + Objects.requireNonNull(entityinsentient); + optional1.ifPresent(entityinsentient::equip); + } + +- if (!world.tryAddFreshEntityWithPassengers(entity)) { ++ entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason ++ // CraftBukkit start ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) { + return Optional.empty(); ++ } ++ if (!world.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER)) { ++ // CraftBukkit end ++ return Optional.empty(); + } else { + TrialSpawner.FlameParticle trialspawner_a = this.isOminous ? TrialSpawner.FlameParticle.OMINOUS : TrialSpawner.FlameParticle.NORMAL; + +@@ -248,6 +260,15 @@ + ObjectArrayList objectarraylist = loottable.getRandomItems(lootparams); + + if (!objectarraylist.isEmpty()) { ++ // CraftBukkit start ++ BlockDispenseLootEvent spawnerDispenseLootEvent = CraftEventFactory.callBlockDispenseLootEvent(world, pos, null, objectarraylist); ++ if (spawnerDispenseLootEvent.isCancelled()) { ++ return; ++ } ++ ++ objectarraylist = new ObjectArrayList<>(spawnerDispenseLootEvent.getDispensedLoot().stream().map(CraftItemStack::asNMSCopy).toList()); ++ // CraftBukkit end ++ + ObjectListIterator objectlistiterator = objectarraylist.iterator(); + + while (objectlistiterator.hasNext()) { +@@ -370,7 +391,7 @@ + } + + public void overrideEntityToSpawn(EntityType entityType, Level world) { +- this.data.reset(); ++ this.data.reset(this); // Paper + this.normalConfig = Holder.direct(((TrialSpawnerConfig) this.normalConfig.value()).withSpawning(entityType)); + this.ominousConfig = Holder.direct(((TrialSpawnerConfig) this.ominousConfig.value()).withSpawning(entityType)); + this.setState(world, TrialSpawnerState.INACTIVE); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch new file mode 100644 index 0000000000..9f48f83f27 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java ++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +@@ -100,9 +100,9 @@ + this.ejectingLootTable = rewardLootTable; + } + +- public void reset() { ++ public void reset(TrialSpawner logic) { // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + this.currentMobs.clear(); +- this.nextSpawnData = Optional.empty(); ++ if (!logic.getConfig().spawnPotentialsDefinition().isEmpty()) this.nextSpawnData = Optional.empty(); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + this.resetStatistics(); + } + +@@ -210,7 +210,7 @@ + } + + public void resetAfterBecomingOminous(TrialSpawner logic, ServerLevel world) { +- Stream stream = this.currentMobs.stream(); ++ Stream stream = this.currentMobs.stream(); // CraftBukkit - decompile error + + Objects.requireNonNull(world); + stream.map(world::getEntity).forEach((entity) -> { +@@ -222,7 +222,7 @@ + entityinsentient.dropPreservedEquipment(world); + } + +- entity.remove(Entity.RemovalReason.DISCARDED); ++ entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause; + } + }); + if (!logic.getOminousConfig().spawnPotentialsDefinition().isEmpty()) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch new file mode 100644 index 0000000000..293d3ea411 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java ++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java +@@ -145,7 +145,7 @@ + yield ACTIVE; + } else if (trialSpawnerData.isCooldownFinished(world)) { + logic.removeOminous(world, pos); +- trialSpawnerData.reset(); ++ trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + yield WAITING_FOR_PLAYERS; + } else { + yield this; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch new file mode 100644 index 0000000000..928e901a76 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch @@ -0,0 +1,83 @@ +--- a/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java +@@ -46,6 +46,13 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.block.BlockDispenseLootEvent; ++import org.bukkit.event.block.VaultDisplayItemEvent; ++// CraftBukkit end ++ + public class VaultBlockEntity extends BlockEntity { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -96,18 +103,18 @@ + dataresult = VaultServerData.CODEC.parse(dynamicops, nbt.get("server_data")); + logger = VaultBlockEntity.LOGGER; + Objects.requireNonNull(logger); +- optional = dataresult.resultOrPartial(logger::error); ++ optional = ((DataResult) dataresult).resultOrPartial(logger::error); // CraftBukkit - decompile error + VaultServerData vaultserverdata = this.serverData; + + Objects.requireNonNull(this.serverData); +- optional.ifPresent(vaultserverdata::set); ++ ((Optional) optional).ifPresent(vaultserverdata::set); // CraftBukkit - decompile error + } + + if (nbt.contains("config")) { + dataresult = VaultConfig.CODEC.parse(dynamicops, nbt.get("config")); + logger = VaultBlockEntity.LOGGER; + Objects.requireNonNull(logger); +- dataresult.resultOrPartial(logger::error).ifPresent((vaultconfig) -> { ++ ((DataResult) dataresult).resultOrPartial(logger::error).ifPresent((vaultconfig) -> { // CraftBukkit - decompile error + this.config = vaultconfig; + }); + } +@@ -116,11 +123,11 @@ + dataresult = VaultSharedData.CODEC.parse(dynamicops, nbt.get("shared_data")); + logger = VaultBlockEntity.LOGGER; + Objects.requireNonNull(logger); +- optional = dataresult.resultOrPartial(logger::error); ++ optional = ((DataResult) dataresult).resultOrPartial(logger::error); // CraftBukkit - decompile error + VaultSharedData vaultshareddata = this.sharedData; + + Objects.requireNonNull(this.sharedData); +- optional.ifPresent(vaultshareddata::set); ++ ((Optional) optional).ifPresent(vaultshareddata::set); // CraftBukkit - decompile error + } + + } +@@ -320,6 +327,14 @@ + if (!list.isEmpty()) { + player.awardStat(Stats.ITEM_USED.get(stack.getItem())); + stack.consume(config.keyItem().getCount(), player); ++ // CraftBukkit start ++ BlockDispenseLootEvent vaultDispenseLootEvent = CraftEventFactory.callBlockDispenseLootEvent(world, pos, player, list); ++ if (vaultDispenseLootEvent.isCancelled()) { ++ return; ++ } ++ ++ list = vaultDispenseLootEvent.getDispensedLoot().stream().map(CraftItemStack::asNMSCopy).toList(); ++ // CraftBukkit end + Server.unlock(world, state, pos, config, serverData, sharedData, list); + serverData.addToRewardedPlayers(player); + sharedData.updateConnectedPlayersWithinRange(world, pos, serverData, config, config.deactivationRange()); +@@ -341,7 +356,15 @@ + sharedData.setDisplayItem(ItemStack.EMPTY); + } else { + ItemStack itemstack = Server.getRandomDisplayItemFromLootTable(world, pos, (ResourceKey) config.overrideLootTableToDisplay().orElse(config.lootTable())); ++ // CraftBukkit start ++ VaultDisplayItemEvent event = CraftEventFactory.callVaultDisplayItemEvent(world, pos, itemstack); ++ if (event.isCancelled()) { ++ return; ++ } + ++ itemstack = CraftItemStack.asNMSCopy(event.getDisplayItem()); ++ // CraftBukkit end ++ + sharedData.setDisplayItem(itemstack); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch new file mode 100644 index 0000000000..859e74b185 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch @@ -0,0 +1,126 @@ +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -20,9 +20,14 @@ + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.SaplingBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; ++// CraftBukkit start ++import net.minecraft.data.worldgen.features.TreeFeatures; ++import org.bukkit.TreeType; ++// CraftBukkit end + + public final class TreeGrower { + +@@ -75,21 +80,22 @@ + } + } + +- return flowersNearby && this.flowers.isPresent() ? (ResourceKey) this.flowers.get() : (ResourceKey) this.tree.orElse((Object) null); ++ return flowersNearby && this.flowers.isPresent() ? (ResourceKey) this.flowers.get() : (ResourceKey) this.tree.orElse(null); // CraftBukkit - decompile error + } + + @Nullable + private ResourceKey> getConfiguredMegaFeature(RandomSource random) { +- return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? (ResourceKey) this.secondaryMegaTree.get() : (ResourceKey) this.megaTree.orElse((Object) null); ++ return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? (ResourceKey) this.secondaryMegaTree.get() : (ResourceKey) this.megaTree.orElse(null); // CraftBukkit - decompile error + } + + public boolean growTree(ServerLevel world, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { + ResourceKey> resourcekey = this.getConfiguredMegaFeature(random); + + if (resourcekey != null) { +- Holder> holder = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey).orElse((Object) null); ++ Holder> holder = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey).orElse(null); // CraftBukkit - decompile error + + if (holder != null) { ++ this.setTreeType(holder); // CraftBukkit + for (int i = 0; i >= -1; --i) { + for (int j = 0; j >= -1; --j) { + if (TreeGrower.isTwoByTwoSapling(state, world, pos, i, j)) { +@@ -120,11 +126,12 @@ + if (resourcekey1 == null) { + return false; + } else { +- Holder> holder1 = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey1).orElse((Object) null); ++ Holder> holder1 = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey1).orElse(null); // CraftBukkit - decompile error + + if (holder1 == null) { + return false; + } else { ++ this.setTreeType(holder1); // CraftBukkit + ConfiguredFeature worldgenfeatureconfigured1 = (ConfiguredFeature) holder1.value(); + BlockState iblockdata2 = world.getFluidState(pos).createLegacyBlock(); + +@@ -165,11 +172,66 @@ + return true; + } + ++ // CraftBukkit start ++ private void setTreeType(Holder> holder) { ++ ResourceKey> worldgentreeabstract = holder.unwrapKey().get(); ++ if (worldgentreeabstract == TreeFeatures.OAK || worldgentreeabstract == TreeFeatures.OAK_BEES_005) { ++ SaplingBlock.treeType = TreeType.TREE; ++ } else if (worldgentreeabstract == TreeFeatures.HUGE_RED_MUSHROOM) { ++ SaplingBlock.treeType = TreeType.RED_MUSHROOM; ++ } else if (worldgentreeabstract == TreeFeatures.HUGE_BROWN_MUSHROOM) { ++ SaplingBlock.treeType = TreeType.BROWN_MUSHROOM; ++ } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE) { ++ SaplingBlock.treeType = TreeType.COCOA_TREE; ++ } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE_NO_VINE) { ++ SaplingBlock.treeType = TreeType.SMALL_JUNGLE; ++ } else if (worldgentreeabstract == TreeFeatures.PINE) { ++ SaplingBlock.treeType = TreeType.TALL_REDWOOD; ++ } else if (worldgentreeabstract == TreeFeatures.SPRUCE) { ++ SaplingBlock.treeType = TreeType.REDWOOD; ++ } else if (worldgentreeabstract == TreeFeatures.ACACIA) { ++ SaplingBlock.treeType = TreeType.ACACIA; ++ } else if (worldgentreeabstract == TreeFeatures.BIRCH || worldgentreeabstract == TreeFeatures.BIRCH_BEES_005) { ++ SaplingBlock.treeType = TreeType.BIRCH; ++ } else if (worldgentreeabstract == TreeFeatures.SUPER_BIRCH_BEES_0002) { ++ SaplingBlock.treeType = TreeType.TALL_BIRCH; ++ } else if (worldgentreeabstract == TreeFeatures.SWAMP_OAK) { ++ SaplingBlock.treeType = TreeType.SWAMP; ++ } else if (worldgentreeabstract == TreeFeatures.FANCY_OAK || worldgentreeabstract == TreeFeatures.FANCY_OAK_BEES_005) { ++ SaplingBlock.treeType = TreeType.BIG_TREE; ++ } else if (worldgentreeabstract == TreeFeatures.JUNGLE_BUSH) { ++ SaplingBlock.treeType = TreeType.JUNGLE_BUSH; ++ } else if (worldgentreeabstract == TreeFeatures.DARK_OAK) { ++ SaplingBlock.treeType = TreeType.DARK_OAK; ++ } else if (worldgentreeabstract == TreeFeatures.MEGA_SPRUCE) { ++ SaplingBlock.treeType = TreeType.MEGA_REDWOOD; ++ } else if (worldgentreeabstract == TreeFeatures.MEGA_PINE) { ++ SaplingBlock.treeType = TreeType.MEGA_PINE; ++ } else if (worldgentreeabstract == TreeFeatures.MEGA_JUNGLE_TREE) { ++ SaplingBlock.treeType = TreeType.JUNGLE; ++ } else if (worldgentreeabstract == TreeFeatures.AZALEA_TREE) { ++ SaplingBlock.treeType = TreeType.AZALEA; ++ } else if (worldgentreeabstract == TreeFeatures.MANGROVE) { ++ SaplingBlock.treeType = TreeType.MANGROVE; ++ } else if (worldgentreeabstract == TreeFeatures.TALL_MANGROVE) { ++ SaplingBlock.treeType = TreeType.TALL_MANGROVE; ++ } else if (worldgentreeabstract == TreeFeatures.CHERRY || worldgentreeabstract == TreeFeatures.CHERRY_BEES_005) { ++ SaplingBlock.treeType = TreeType.CHERRY; ++ } else if (worldgentreeabstract == TreeFeatures.PALE_OAK || worldgentreeabstract == TreeFeatures.PALE_OAK_BONEMEAL) { ++ SaplingBlock.treeType = TreeType.PALE_OAK; ++ } else if (worldgentreeabstract == TreeFeatures.PALE_OAK_CREAKING) { ++ SaplingBlock.treeType = TreeType.PALE_OAK_CREAKING; ++ } else { ++ throw new IllegalArgumentException("Unknown tree generator " + worldgentreeabstract); ++ } ++ } ++ // CraftBukkit end ++ + static { +- Function function = (worldgentreeprovider) -> { ++ Function function = (worldgentreeprovider) -> { // CraftBukkit - decompile error + return worldgentreeprovider.name; + }; +- Map map = TreeGrower.GROWERS; ++ Map map = TreeGrower.GROWERS; // CraftBukkit - decompile error + + Objects.requireNonNull(map); + CODEC = Codec.stringResolver(function, map::get); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch new file mode 100644 index 0000000000..d269336ba6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch @@ -0,0 +1,180 @@ +--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -44,6 +44,13 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import com.google.common.collect.ImmutableList; ++import java.util.AbstractList; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockPistonRetractEvent; ++import org.bukkit.event.block.BlockPistonExtendEvent; ++// CraftBukkit end + + public class PistonBaseBlock extends DirectionalBlock { + +@@ -155,6 +162,18 @@ + } + } + ++ // CraftBukkit start ++ // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down ++ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // ++ // if (event.isCancelled()) { ++ // return; ++ // } ++ // } ++ // PAIL: checkME - what happened to setTypeAndData? ++ // CraftBukkit end + world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); + } + +@@ -197,6 +216,12 @@ + @Override + protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); + + if (!world.isClientSide) { +@@ -229,8 +254,15 @@ + + BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); + ++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires ++ if (!this.isSticky) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.setBlock(pos, iblockdata2, 20); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change + world.blockUpdated(pos, iblockdata2.getBlock()); + iblockdata2.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -255,11 +287,25 @@ + if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) { + this.moveBlocks(world, pos, enumdirection, false); + } else { ++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air) ++ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.removeBlock(pos.relative(enumdirection), false); + } + } + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.removeBlock(headPos, false); ++ } else { ++ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +@@ -335,7 +381,49 @@ + BlockState[] aiblockdata = new BlockState[list.size() + list2.size()]; + Direction enumdirection1 = extend ? dir : dir.getOpposite(); + int i = 0; ++ // CraftBukkit start ++ final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ ++ final List moved = pistonextendschecker.getToPush(); ++ final List broken = pistonextendschecker.getToDestroy(); ++ ++ List blocks = new AbstractList() { + ++ @Override ++ public int size() { ++ return moved.size() + broken.size(); ++ } ++ ++ @Override ++ public org.bukkit.block.Block get(int index) { ++ if (index >= this.size() || index < 0) { ++ throw new ArrayIndexOutOfBoundsException(index); ++ } ++ BlockPos pos = (BlockPos) (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 (extend) { ++ event = new BlockPistonExtendEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1)); ++ } else { ++ event = new BlockPistonRetractEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1)); ++ } ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ for (BlockPos b : broken) { ++ world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3); ++ } ++ for (BlockPos b : moved) { ++ world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3); ++ b = b.relative(enumdirection1); ++ world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3); ++ } ++ return false; ++ } ++ // CraftBukkit end ++ + BlockPos blockposition3; + int j; + BlockState iblockdata1; +@@ -345,7 +433,7 @@ + iblockdata1 = world.getBlockState(blockposition3); + BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; + +- dropResources(iblockdata1, world, blockposition3, tileentity); ++ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - Add BlockBreakBlockEvent + world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); + world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); + if (!iblockdata1.is(BlockTags.FIRE)) { +@@ -358,13 +446,25 @@ + BlockState iblockdata2; + + for (j = list.size() - 1; j >= 0; --j) { +- blockposition3 = (BlockPos) list.get(j); +- iblockdata1 = world.getBlockState(blockposition3); ++ // Paper start - fix a variety of piston desync dupes ++ boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; ++ BlockPos oldPos = blockposition3 = (BlockPos) list.get(j); ++ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; ++ // Paper end - fix a variety of piston desync dupes + blockposition3 = blockposition3.relative(enumdirection1); + map.remove(blockposition3); + iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir); + world.setBlock(blockposition3, iblockdata2, 68); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(j), dir, extend, false)); ++ // Paper start - fix a variety of piston desync dupes ++ if (!allowDesync) { ++ iblockdata1 = world.getBlockState(oldPos); ++ map.replace(oldPos, iblockdata1); ++ } ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? (BlockState) list1.get(j) : iblockdata1, dir, extend, false)); ++ if (!allowDesync) { ++ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block ++ } ++ // Paper end - fix a variety of piston desync dupes + aiblockdata[i++] = iblockdata1; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch new file mode 100644 index 0000000000..03d9e64403 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -306,7 +306,7 @@ + if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) { + BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos); + if (blockState.isAir()) { +- world.setBlock(pos, blockEntity.movedState, 84); ++ world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication ? 84 : (84 | Block.UPDATE_CLIENTS)); // Paper - fix a variety of piston desync dupes; force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air + Block.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3); + } else { + if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch new file mode 100644 index 0000000000..56a6b42a76 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -0,0 +1,193 @@ +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -46,6 +46,7 @@ + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.EmptyBlockGetter; + import net.minecraft.world.level.Explosion; +@@ -83,6 +84,8 @@ + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import net.minecraft.world.level.ServerExplosion; ++// CraftBukkit end + + public abstract class BlockBehaviour implements FeatureElement { + +@@ -156,9 +159,18 @@ + + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {} + +- protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {} ++ protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { ++ org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot ++ } + ++ // CraftBukkit start ++ protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, @Nullable UseOnContext context) { ++ this.onPlace(iblockdata, world, blockposition, iblockdata1, flag); ++ } ++ // CraftBukkit end ++ + protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { ++ org.spigotmc.AsyncCatcher.catchOp("block remove"); // Spigot + if (state.hasBlockEntity() && !state.is(newState.getBlock())) { + world.removeBlockEntity(pos); + } +@@ -166,7 +178,7 @@ + } + + protected void onExplosionHit(BlockState state, ServerLevel world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { +- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { ++ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed + Block block = state.getBlock(); + boolean flag = explosion.getIndirectSourceEntity() instanceof Player; + +@@ -174,8 +186,10 @@ + BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; + LootParams.Builder lootparams_a = (new LootParams.Builder(world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity()); + +- if (explosion.getBlockInteraction() == Explosion.BlockInteraction.DESTROY_WITH_DECAY) { +- lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius()); ++ // CraftBukkit start - add yield ++ if (explosion instanceof ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) { ++ lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield); ++ // CraftBukkit end + } + + state.spawnAfterBreak(world, pos, ItemStack.EMPTY, flag); +@@ -243,7 +257,7 @@ + } + + protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); ++ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + protected boolean canBeReplaced(BlockState state, Fluid fluid) { +@@ -851,7 +865,15 @@ + this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles; + this.instrument = blockbase_info.instrument; + this.replaceable = blockbase_info.replaceable; ++ } ++ // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time ++ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; ++ ++ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { ++ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState()); ++ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); + } ++ // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time + + private boolean calculateSolid() { + if (((Block) this.owner).properties.forceSolidOn) { +@@ -873,12 +895,14 @@ + } + } + ++ protected boolean shapeExceedsCube = true; // Paper - moved from actual method to here + public void initCache() { + this.fluidState = ((Block) this.owner).getFluidState(this.asState()); + this.isRandomlyTicking = ((Block) this.owner).isRandomlyTicking(this.asState()); + if (!this.getBlock().hasDynamicShape()) { + this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); + } ++ this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here + + this.legacySolid = this.calculateSolid(); + this.occlusionShape = this.canOcclude ? ((Block) this.owner).getOcclusionShape(this.asState()) : Shapes.empty(); +@@ -925,6 +949,12 @@ + return this.legacySolid; + } + ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed ++ + public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { + return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); + } +@@ -945,19 +975,19 @@ + return this.occlusionShape; + } + +- public boolean hasLargeCollisionShape() { +- return this.cache == null || this.cache.largeCollisionShape; ++ public final boolean hasLargeCollisionShape() { // Paper ++ return this.shapeExceedsCube; // Paper - moved into shape cache init + } + +- public boolean useShapeForLightOcclusion() { ++ public final boolean useShapeForLightOcclusion() { // Paper - Perf: Final for inlining + return this.useShapeForLightOcclusion; + } + +- public int getLightEmission() { ++ public final int getLightEmission() { // Paper - Perf: Final for inlining + return this.lightEmission; + } + +- public boolean isAir() { ++ public final boolean isAir() { // Paper - Perf: Final for inlining + return this.isAir; + } + +@@ -1028,14 +1058,14 @@ + } + + public PushReaction getPistonPushReaction() { +- return this.pushReaction; ++ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + public boolean isSolidRender() { + return this.solidRender; + } + +- public boolean canOcclude() { ++ public final boolean canOcclude() { // Paper - Perf: Final for inlining + return this.canOcclude; + } + +@@ -1125,7 +1155,13 @@ + } + + public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify) { +- this.getBlock().onPlace(this.asState(), world, pos, state, notify); ++ // CraftBukkit start ++ this.onPlace(world, pos, state, notify, null); ++ } ++ ++ public void onPlace(Level world, BlockPos blockposition, BlockState iblockdata, boolean flag, @Nullable UseOnContext context) { ++ this.getBlock().onPlace(this.asState(), world, blockposition, iblockdata, flag, context); ++ // CraftBukkit end + } + + public void onRemove(Level world, BlockPos pos, BlockState state, boolean moved) { +@@ -1154,6 +1190,7 @@ + + public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience); ++ if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping + } + + public List getDrops(LootParams.Builder builder) { +@@ -1250,11 +1287,11 @@ + return this.getBlock().builtInRegistryHolder().is(key); + } + +- public FluidState getFluidState() { ++ public final FluidState getFluidState() { // Paper - Perf: Final for inlining + return this.fluidState; + } + +- public boolean isRandomlyTicking() { ++ public final boolean isRandomlyTicking() { // Paper - Perf: Final for inlining + return this.isRandomlyTicking; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch new file mode 100644 index 0000000000..77d0d6d1c5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/state/BlockState.java ++++ b/net/minecraft/world/level/block/state/BlockState.java +@@ -10,6 +10,16 @@ + public class BlockState extends BlockBehaviour.BlockStateBase { + public static final Codec CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); + ++ // Paper start - optimise getType calls ++ org.bukkit.Material cachedMaterial; ++ ++ public final org.bukkit.Material getBukkitMaterial() { ++ if (this.cachedMaterial == null) { ++ this.cachedMaterial = org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.getBlock()); ++ } ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls + public BlockState(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + super(block, propertyMap, codec); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch new file mode 100644 index 0000000000..95194b370f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/state/properties/EnumProperty.java ++++ b/net/minecraft/world/level/block/state/properties/EnumProperty.java +@@ -59,8 +59,7 @@ + return this.ordinalToIndex[enum_.ordinal()]; + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper - Perf: Optimize hashCode/equals + if (this == object) { + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch new file mode 100644 index 0000000000..6542f0bc6f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/block/state/properties/IntegerProperty.java ++++ b/net/minecraft/world/level/block/state/properties/IntegerProperty.java +@@ -28,8 +28,7 @@ + return this.values; + } + +- @Override +- public boolean equals(Object object) { ++ public boolean equals_unused(Object object) { // Paper - Perf: Optimize hashCode/equals + if (this == object) { + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch new file mode 100644 index 0000000000..e0a6a659ba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/state/properties/Property.java ++++ b/net/minecraft/world/level/block/state/properties/Property.java +@@ -72,7 +72,7 @@ + + @Override + public boolean equals(Object object) { +- return this == object || object instanceof Property property && this.clazz.equals(property.clazz) && this.name.equals(property.name); ++ return this == object; // Paper - Perf: Optimize hashCode/equals + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch b/paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch new file mode 100644 index 0000000000..c2615ad43a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch @@ -0,0 +1,99 @@ +--- a/net/minecraft/world/level/border/WorldBorder.java ++++ b/net/minecraft/world/level/border/WorldBorder.java +@@ -30,6 +30,7 @@ + int absoluteMaxSize = 29999984; + private WorldBorder.BorderExtent extent = new WorldBorder.StaticBorderExtent(5.9999968E7D); + public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0D, 0.0D, 0.2D, 5.0D, 5, 15, 5.9999968E7D, 0L, 0.0D); ++ public net.minecraft.server.level.ServerLevel world; // CraftBukkit + + public WorldBorder() {} + +@@ -45,6 +46,18 @@ + return this.isWithinBounds((double) chunkPos.getMinBlockX(), (double) chunkPos.getMinBlockZ()) && this.isWithinBounds((double) chunkPos.getMaxBlockX(), (double) chunkPos.getMaxBlockZ()); + } + ++ // Paper start - Bound treasure maps to world border ++ private final BlockPos.MutableBlockPos mutPos = new BlockPos.MutableBlockPos(); ++ public boolean isBlockInBounds(int chunkX, int chunkZ) { ++ this.mutPos.set(chunkX, 64, chunkZ); ++ return this.isWithinBounds(this.mutPos); ++ } ++ public boolean isChunkInBounds(int chunkX, int chunkZ) { ++ this.mutPos.set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15); ++ return this.isWithinBounds(this.mutPos); ++ } ++ // Paper end - Bound treasure maps to world border ++ + public boolean isWithinBounds(AABB box) { + return this.isWithinBounds(box.minX, box.minZ, box.maxX - 9.999999747378752E-6D, box.maxZ - 9.999999747378752E-6D); + } +@@ -135,6 +148,14 @@ + } + + public void setCenter(double x, double z) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); ++ if (!event.callEvent()) return; ++ x = event.getNewCenter().getX(); ++ z = event.getNewCenter().getZ(); ++ } ++ // Paper end - Add worldborder events + this.centerX = x; + this.centerZ = z; + this.extent.onCenterChange(); +@@ -161,6 +182,17 @@ + } + + public void setSize(double size) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); ++ if (!event.callEvent()) return; ++ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition ++ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); ++ return; ++ } ++ size = event.getNewSize(); ++ } ++ // Paper end - Add worldborder events + this.extent = new WorldBorder.StaticBorderExtent(size); + Iterator iterator = this.getListeners().iterator(); + +@@ -173,6 +205,20 @@ + } + + public void lerpSizeBetween(double fromSize, double toSize, long time) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type; ++ if (fromSize == toSize) { // new size = old size ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. ++ } else { ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; ++ } ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); ++ if (!event.callEvent()) return; ++ toSize = event.getNewSize(); ++ time = event.getDuration(); ++ } ++ // Paper end - Add worldborder events + this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); + Iterator iterator = this.getListeners().iterator(); + +@@ -189,6 +235,7 @@ + } + + public void addListener(BorderChangeListener listener) { ++ if (this.listeners.contains(listener)) return; // CraftBukkit + this.listeners.add(listener); + } + +@@ -483,6 +530,7 @@ + + @Override + public WorldBorder.BorderExtent update() { ++ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - Add worldborder events + return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch new file mode 100644 index 0000000000..54c4c6342e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch @@ -0,0 +1,88 @@ +--- a/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -65,7 +65,7 @@ + protected final ShortList[] postProcessing; + private volatile boolean unsaved; + private volatile boolean isLightCorrect; +- protected final ChunkPos chunkPos; ++ protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key + private long inhabitedTime; + /** @deprecated */ + @Nullable +@@ -85,8 +85,14 @@ + protected final LevelHeightAccessor levelHeightAccessor; + protected final LevelChunkSection[] sections; + ++ // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading. ++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); ++ public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); ++ // CraftBukkit end ++ + public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { +- this.chunkPos = pos; ++ this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups ++ this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key + this.upgradeData = upgradeData; + this.levelHeightAccessor = heightLimitView; + this.sections = new LevelChunkSection[heightLimitView.getSectionsCount()]; +@@ -103,7 +109,11 @@ + } + + ChunkAccess.replaceMissingSections(biomeRegistry, this.sections); ++ // CraftBukkit start ++ this.biomeRegistry = biomeRegistry; + } ++ public final Registry biomeRegistry; ++ // CraftBukkit end + + private static void replaceMissingSections(Registry biomeRegistry, LevelChunkSection[] sectionArray) { + for (int i = 0; i < sectionArray.length; ++i) { +@@ -275,6 +285,7 @@ + public boolean tryMarkSaved() { + if (this.unsaved) { + this.unsaved = false; ++ this.persistentDataContainer.dirty(false); // CraftBukkit - SPIGOT-6814: chunk was saved, pdc is no longer dirty + return true; + } else { + return false; +@@ -282,7 +293,7 @@ + } + + public boolean isUnsaved() { +- return this.unsaved; ++ return this.unsaved || this.persistentDataContainer.dirty(); // CraftBukkit - SPIGOT-6814: chunk is unsaved if pdc was mutated + } + + public abstract ChunkStatus getPersistedStatus(); +@@ -458,10 +469,31 @@ + + crashreportsystemdetails.setDetail("Location", () -> { + return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ); ++ }); ++ throw new ReportedException(crashreport); ++ } ++ } ++ ++ // CraftBukkit start ++ public void setBiome(int i, int j, int k, Holder biome) { ++ try { ++ int l = QuartPos.fromBlock(this.getMinY()); ++ int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1; ++ int j1 = Mth.clamp(j, l, i1); ++ int k1 = this.getSectionIndex(QuartPos.toBlock(j1)); ++ ++ this.sections[k1].setBiome(i & 3, j1 & 3, k & 3, biome); ++ } catch (Throwable throwable) { ++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Setting biome"); ++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being set"); ++ ++ crashreportsystemdetails.setDetail("Location", () -> { ++ return CrashReportCategory.formatLocation(this, i, j, k); + }); + throw new ReportedException(crashreport); + } + } ++ // CraftBukkit end + + public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler) { + ChunkPos chunkcoordintpair = this.getPos(); diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch new file mode 100644 index 0000000000..337768ffa6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch @@ -0,0 +1,224 @@ +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -108,8 +108,8 @@ + + protected abstract MapCodec codec(); + +- public ChunkGeneratorStructureState createState(HolderLookup structureSetRegistry, RandomState noiseConfig, long seed) { +- return ChunkGeneratorStructureState.createForNormal(noiseConfig, seed, this.biomeSource, structureSetRegistry); ++ public ChunkGeneratorStructureState createState(HolderLookup holderlookup, RandomState randomstate, long i, org.spigotmc.SpigotWorldConfig conf) { // Spigot ++ return ChunkGeneratorStructureState.createForNormal(randomstate, i, this.biomeSource, holderlookup, conf); // Spigot + } + + public Optional>> getTypeNameForDataFixer() { +@@ -127,6 +127,24 @@ + + @Nullable + public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { ++ // Paper start - StructuresLocateEvent ++ final org.bukkit.World bukkitWorld = world.getWorld(); ++ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center); ++ final List apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList(); ++ if (!apiStructures.isEmpty()) { ++ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures); ++ if (!event.callEvent()) { ++ return null; ++ } ++ if (event.getResult() != null) { ++ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure()))); ++ } ++ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin()); ++ radius = event.getRadius(); ++ skipReferencedStructures = event.shouldFindUnexplored(); ++ structures = HolderSet.direct(api -> world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures()); ++ } ++ // Paper end + ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState(); + Map>> map = new Object2ObjectArrayMap(); + Iterator iterator = structures.iterator(); +@@ -223,6 +241,7 @@ + + while (iterator.hasNext()) { + ChunkPos chunkcoordintpair = (ChunkPos) iterator.next(); ++ if (!world.paperConfig().environment.locateStructuresOutsideWorldBorder && !world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper - Bound treasure maps to world border + + blockposition_mutableblockposition.set(SectionPos.sectionToBlockCoord(chunkcoordintpair.x, 8), 32, SectionPos.sectionToBlockCoord(chunkcoordintpair.z, 8)); + double d1 = blockposition_mutableblockposition.distSqr(center); +@@ -247,12 +266,15 @@ + int i1 = placement.spacing(); + + for (int j1 = -radius; j1 <= radius; ++j1) { +- boolean flag1 = j1 == -radius || j1 == radius; ++ // Paper start - Perf: iterate over border chunks instead of entire square chunk area ++ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER + +- for (int k1 = -radius; k1 <= radius; ++k1) { +- boolean flag2 = k1 == -radius || k1 == radius; ++ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) { ++ // boolean flag2 = k1 == -radius || k1 == radius; + +- if (flag1 || flag2) { ++ // if (flag1 || flag2) { ++ if (true) { ++ // Paper end - Perf: iterate over border chunks instead of entire square chunk area + int l1 = centerChunkX + i1 * j1; + int i2 = centerChunkZ + i1 * k1; + ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2); +@@ -312,29 +334,29 @@ + } + } + +- public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) { +- ChunkPos chunkcoordintpair = chunk.getPos(); ++ public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { // CraftBukkit ++ ChunkPos chunkcoordintpair = ichunkaccess.getPos(); + + if (!SharedConstants.debugVoidTerrain(chunkcoordintpair)) { +- SectionPos sectionposition = SectionPos.of(chunkcoordintpair, world.getMinSectionY()); ++ SectionPos sectionposition = SectionPos.of(chunkcoordintpair, generatoraccessseed.getMinSectionY()); + BlockPos blockposition = sectionposition.origin(); +- Registry iregistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE); ++ Registry iregistry = generatoraccessseed.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Map> map = (Map) iregistry.stream().collect(Collectors.groupingBy((structure) -> { + return structure.step().ordinal(); + })); + List list = (List) this.featuresPerStep.get(); + WorldgenRandom seededrandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); +- long i = seededrandom.setDecorationSeed(world.getSeed(), blockposition.getX(), blockposition.getZ()); ++ long i = seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), blockposition.getX(), blockposition.getZ()); + Set> set = new ObjectArraySet(); + + ChunkPos.rangeClosed(sectionposition.chunk(), 1).forEach((chunkcoordintpair1) -> { +- ChunkAccess ichunkaccess1 = world.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z); ++ ChunkAccess ichunkaccess1 = generatoraccessseed.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z); + LevelChunkSection[] achunksection = ichunkaccess1.getSections(); + int j = achunksection.length; + + for (int k = 0; k < j; ++k) { + LevelChunkSection chunksection = achunksection[k]; +- PalettedContainerRO palettedcontainerro = chunksection.getBiomes(); ++ PalettedContainerRO> palettedcontainerro = chunksection.getBiomes(); // CraftBukkit - decompile error + + Objects.requireNonNull(set); + palettedcontainerro.getAll(set::add); +@@ -345,7 +367,7 @@ + int j = list.size(); + + try { +- Registry iregistry1 = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); ++ Registry iregistry1 = generatoraccessseed.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + int k = Math.max(GenerationStep.Decoration.values().length, j); + + for (int l = 0; l < k; ++l) { +@@ -353,7 +375,7 @@ + Iterator iterator; + CrashReportCategory crashreportsystemdetails; + +- if (structureAccessor.shouldGenerateStructures()) { ++ if (structuremanager.shouldGenerateStructures()) { + List list1 = (List) map.getOrDefault(l, Collections.emptyList()); + + for (iterator = list1.iterator(); iterator.hasNext(); ++i1) { +@@ -368,9 +390,9 @@ + }; + + try { +- world.setCurrentlyGenerating(supplier); +- structureAccessor.startsForStructure(sectionposition, structure).forEach((structurestart) -> { +- structurestart.placeInChunk(world, structureAccessor, this, seededrandom, ChunkGenerator.getWritableArea(chunk), chunkcoordintpair); ++ generatoraccessseed.setCurrentlyGenerating(supplier); ++ structuremanager.startsForStructure(sectionposition, structure).forEach((structurestart) -> { ++ structurestart.placeInChunk(generatoraccessseed, structuremanager, this, seededrandom, ChunkGenerator.getWritableArea(ichunkaccess), chunkcoordintpair); + }); + } catch (Exception exception) { + CrashReport crashreport = CrashReport.forThrowable(exception, "Feature placement"); +@@ -418,11 +440,18 @@ + return (String) optional.orElseGet(placedfeature::toString); + }; + +- seededrandom.setFeatureSeed(i, l1, l); ++ // Paper start - Configurable feature seeds; change populationSeed used in random ++ long featurePopulationSeed = i; ++ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature()); ++ if (configFeatureSeed != -1) { ++ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above ++ } ++ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l); ++ // Paper end - Configurable feature seeds + + try { +- world.setCurrentlyGenerating(supplier1); +- placedfeature.placeWithBiomeCheck(world, this, seededrandom, blockposition); ++ generatoraccessseed.setCurrentlyGenerating(supplier1); ++ placedfeature.placeWithBiomeCheck(generatoraccessseed, this, seededrandom, blockposition); + } catch (Exception exception1) { + CrashReport crashreport1 = CrashReport.forThrowable(exception1, "Feature placement"); + +@@ -435,15 +464,42 @@ + } + } + +- world.setCurrentlyGenerating((Supplier) null); ++ generatoraccessseed.setCurrentlyGenerating((Supplier) null); + } catch (Exception exception2) { + CrashReport crashreport2 = CrashReport.forThrowable(exception2, "Biome decoration"); + + crashreport2.addCategory("Generation").setDetail("CenterX", (Object) chunkcoordintpair.x).setDetail("CenterZ", (Object) chunkcoordintpair.z).setDetail("Decoration Seed", (Object) i); + throw new ReportedException(crashreport2); ++ } ++ } ++ } ++ ++ // CraftBukkit start ++ public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) { ++ this.applyBiomeDecoration(world, chunk, structureAccessor, true); ++ } ++ ++ public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { ++ if (vanilla) { ++ this.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); ++ } ++ ++ org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld(); ++ // only call when a populator is present (prevents unnecessary entity conversion) ++ if (!world.getPopulators().isEmpty()) { ++ org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos()); ++ int x = ichunkaccess.getPos().x; ++ int z = ichunkaccess.getPos().z; ++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { ++ WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed())); ++ seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z); ++ populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion); + } ++ limitedRegion.saveEntities(); ++ limitedRegion.breakLink(); + } + } ++ // CraftBukkit end + + private static BoundingBox getWritableArea(ChunkAccess chunk) { + ChunkPos chunkcoordintpair = chunk.getPos(); +@@ -521,7 +577,7 @@ + } + } + +- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) { ++ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs + if (list.size() == 1) { + this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition, dimension); + } else { +@@ -582,6 +638,14 @@ + StructureStart structurestart = structure.generate(weightedEntry.structure(), dimension, dynamicRegistryManager, this, this.biomeSource, noiseConfig, structureManager, seed, pos, j, chunk, predicate); + + if (structurestart.isValid()) { ++ // CraftBukkit start ++ BoundingBox box = structurestart.getBoundingBox(); ++ org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureAccessor.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), pos.x, pos.z); ++ org.bukkit.Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return true; ++ } ++ // CraftBukkit end + structureAccessor.setStartForStructure(sectionPos, structure, structurestart, chunk); + return true; + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch new file mode 100644 index 0000000000..f9ebf0e3b1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch @@ -0,0 +1,175 @@ +--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.world.level.chunk; + + import com.google.common.base.Stopwatch; +@@ -33,6 +34,11 @@ + import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; + import org.slf4j.Logger; + ++// Spigot start ++import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; ++import org.spigotmc.SpigotWorldConfig; ++// Spigot end ++ + public class ChunkGeneratorStructureState { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -44,22 +50,109 @@ + private final Map>> ringPositions = new Object2ObjectArrayMap(); + private boolean hasGeneratedPositions; + private final List> possibleStructureSets; ++ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs + +- public static ChunkGeneratorStructureState createForFlat(RandomState noiseConfig, long seed, BiomeSource biomeSource, Stream> structureSets) { +- List> list = structureSets.filter((holder) -> { +- return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), biomeSource); ++ public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream> stream, SpigotWorldConfig conf) { // Spigot ++ List> list = stream.filter((holder) -> { ++ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager); + }).toList(); + +- return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, 0L, list); ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot + } + +- public static ChunkGeneratorStructureState createForNormal(RandomState noiseConfig, long seed, BiomeSource biomeSource, HolderLookup structureSetRegistry) { +- List> list = (List) structureSetRegistry.listElements().filter((holder_c) -> { +- return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), biomeSource); ++ public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup holderlookup, SpigotWorldConfig conf) { // Spigot ++ List> list = (List) holderlookup.listElements().filter((holder_c) -> { ++ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager); + }).collect(Collectors.toUnmodifiableList()); + +- return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, seed, list); ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot ++ } ++ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key ++ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement { ++ public final net.minecraft.resources.ResourceKey key; ++ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) { ++ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType); ++ this.key = key; ++ } ++ } ++ // Paper end - Add missing structure set seed configs ++ ++ // Spigot start ++ private static List> injectSpigot(List> list, SpigotWorldConfig conf) { ++ return list.stream().map((holder) -> { ++ StructureSet structureset = holder.value(); ++ final Holder newHolder; // Paper - Add missing structure set seed configs ++ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path ++ String name = holder.unwrapKey().orElseThrow().location().getPath(); ++ int seed = randomConfig.salt; ++ ++ switch (name) { ++ case "desert_pyramids": ++ seed = conf.desertSeed; ++ break; ++ case "end_cities": ++ seed = conf.endCitySeed; ++ break; ++ case "nether_complexes": ++ seed = conf.netherSeed; ++ break; ++ case "igloos": ++ seed = conf.iglooSeed; ++ break; ++ case "jungle_temples": ++ seed = conf.jungleSeed; ++ break; ++ case "woodland_mansions": ++ seed = conf.mansionSeed; ++ break; ++ case "ocean_monuments": ++ seed = conf.monumentSeed; ++ break; ++ case "nether_fossils": ++ seed = conf.fossilSeed; ++ break; ++ case "ocean_ruins": ++ seed = conf.oceanSeed; ++ break; ++ case "pillager_outposts": ++ seed = conf.outpostSeed; ++ break; ++ case "ruined_portals": ++ seed = conf.portalSeed; ++ break; ++ case "shipwrecks": ++ seed = conf.shipwreckSeed; ++ break; ++ case "swamp_huts": ++ seed = conf.swampSeed; ++ break; ++ case "villages": ++ seed = conf.villageSeed; ++ break; ++ // Paper start - Add missing structure set seed configs ++ case "ancient_cities": ++ seed = conf.ancientCitySeed; ++ break; ++ case "trail_ruins": ++ seed = conf.trailRuinsSeed; ++ break; ++ case "trial_chambers": ++ seed = conf.trialChambersSeed; ++ break; ++ // Paper end - Add missing structure set seed configs ++ } ++ ++ // Paper start - Add missing structure set seed configs ++ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); ++ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here ++ } else { ++ newHolder = holder; ++ } ++ return newHolder; ++ // Paper end - Add missing structure set seed configs ++ }).collect(Collectors.toUnmodifiableList()); + } ++ // Spigot end + + private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) { + Stream> stream = structureSet.structures().stream().flatMap((structureset_a) -> { +@@ -73,12 +166,13 @@ + return stream.anyMatch(set::contains); + } + +- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets) { ++ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs + this.randomState = noiseConfig; + this.levelSeed = structureSeed; + this.biomeSource = biomeSource; + this.concentricRingsSeed = concentricRingSeed; + this.possibleStructureSets = structureSets; ++ this.conf = conf; // Paper - Add missing structure set seed configs + } + + public List> possibleStructureSets() { +@@ -132,7 +226,13 @@ + HolderSet holderset = placement.preferredBiomes(); + RandomSource randomsource = RandomSource.create(); + ++ // Paper start - Add missing structure set seed configs ++ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { ++ randomsource.setSeed(this.conf.strongholdSeed); ++ } else { ++ // Paper end - Add missing structure set seed configs + randomsource.setSeed(this.concentricRingsSeed); ++ } // Paper - Add missing structure set seed configs + double d0 = randomsource.nextDouble() * Math.PI * 2.0D; + int l = 0; + int i1 = 0; +@@ -209,7 +309,7 @@ + + for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) { + for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) { +- if (structureplacement.isStructureChunk(this, l, i1)) { ++ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch new file mode 100644 index 0000000000..e1be97122d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch @@ -0,0 +1,7 @@ +--- a/net/minecraft/world/level/chunk/DataLayer.java ++++ b/net/minecraft/world/level/chunk/DataLayer.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.world.level.chunk; + + import java.util.Arrays; diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch new file mode 100644 index 0000000000..1ccf6729a6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -25,6 +25,12 @@ + public BlockState getBlockState(BlockPos pos) { + return Blocks.VOID_AIR.defaultBlockState(); + } ++ // Paper start ++ @Override ++ public BlockState getBlockState(final int x, final int y, final int z) { ++ return Blocks.VOID_AIR.defaultBlockState(); ++ } ++ // Paper end + + @Nullable + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch new file mode 100644 index 0000000000..ce7c9bcdcb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/level/chunk/HashMapPalette.java ++++ b/net/minecraft/world/level/chunk/HashMapPalette.java +@@ -20,7 +20,7 @@ + } + + public HashMapPalette(IdMap idList, int indexBits, PaletteResize listener) { +- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits)); ++ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap + } + + private HashMapPalette(IdMap idList, int indexBits, PaletteResize listener, CrudeIncrementalIntIdentityHashBiMap map) { +@@ -38,10 +38,16 @@ + public int idFor(T object) { + int i = this.values.getId(object); + if (i == -1) { +- i = this.values.add(object); +- if (i >= 1 << this.bits) { ++ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize ++ // We use size() instead of the result from add(K) ++ // This avoids adding another object unnecessarily ++ // Without this change, + 2 would be required in the constructor ++ if (this.values.size() >= 1 << this.bits) { + i = this.resizeHandler.onResize(this.bits + 1, object); ++ } else { ++ i = this.values.add(object); + } ++ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize + } + + return i; diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch new file mode 100644 index 0000000000..d33242d709 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -0,0 +1,409 @@ +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -79,7 +79,7 @@ + }; + private final Map tickersInLevel; + public boolean loaded; +- public final Level level; ++ public final ServerLevel level; // CraftBukkit - type + @Nullable + private Supplier fullStatus; + @Nullable +@@ -98,7 +98,7 @@ + this.tickersInLevel = Maps.newHashMap(); + this.unsavedListener = (chunkcoordintpair1) -> { + }; +- this.level = world; ++ this.level = (ServerLevel) world; // CraftBukkit - type + this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); + Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); + int j = aheightmap_type.length; +@@ -116,6 +116,15 @@ + this.fluidTicks = fluidTickScheduler; + } + ++ // CraftBukkit start ++ public boolean mustNotSave; ++ public boolean needsDecoration; ++ // CraftBukkit end ++ ++ // Paper start ++ boolean loadedTicketLevel; ++ // Paper end ++ + public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { + this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); + if (!Collections.disjoint(protoChunk.pendingBlockEntities.keySet(), protoChunk.blockEntities.keySet())) { +@@ -151,6 +160,10 @@ + this.skyLightSources = protoChunk.skyLightSources; + this.setLightCorrect(protoChunk.isLightCorrect()); + this.markUnsaved(); ++ this.needsDecoration = true; // CraftBukkit ++ // CraftBukkit start ++ this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading. ++ // CraftBukkit end + } + + public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) { +@@ -187,7 +200,14 @@ + return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time)); + } + ++ // Paper start + @Override ++ public long getInhabitedTime() { ++ return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime; ++ } ++ // Paper end ++ ++ @Override + public GameEventListenerRegistry getListenerRegistry(int ySectionCoord) { + Level world = this.level; + +@@ -200,8 +220,25 @@ + } + } + ++ // Paper start - Perf: Reduce instructions and provide final method ++ public BlockState getBlockState(final int x, final int y, final int z) { ++ return this.getBlockStateFinal(x, y, z); ++ } ++ public BlockState getBlockStateFinal(final int x, final int y, final int z) { ++ // Copied and modified from below ++ final int sectionIndex = this.getSectionIndex(y); ++ if (sectionIndex < 0 || sectionIndex >= this.sections.length ++ || this.sections[sectionIndex].nonEmptyBlockCount == 0) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15); ++ } + @Override + public BlockState getBlockState(BlockPos pos) { ++ if (true) { ++ return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ // Paper end - Perf: Reduce instructions and provide final method + int i = pos.getX(); + int j = pos.getY(); + int k = pos.getZ(); +@@ -243,24 +280,38 @@ + } + } + ++ // Paper start - If loaded util + @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return this.getFluidState(blockposition); ++ } ++ ++ @Override ++ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) { ++ return this.getBlockState(blockposition); ++ } ++ // Paper end ++ ++ @Override + public FluidState getFluidState(BlockPos pos) { + return this.getFluidState(pos.getX(), pos.getY(), pos.getZ()); + } + + public FluidState getFluidState(int x, int y, int z) { +- try { +- int l = this.getSectionIndex(y); ++ // Paper start - Perf: Optimise Chunk#getFluid ++ // try { // Remove try catch ++ int index = this.getSectionIndex(y); ++ if (index >= 0 && index < this.sections.length) { ++ LevelChunkSection chunksection = this.sections[index]; + +- if (l >= 0 && l < this.sections.length) { +- LevelChunkSection chunksection = this.sections[l]; +- + if (!chunksection.hasOnlyAir()) { +- return chunksection.getFluidState(x & 15, y & 15, z & 15); ++ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); ++ // Paper end - Perf: Optimise Chunk#getFluid + } + } + + return Fluids.EMPTY.defaultFluidState(); ++ /* // Paper - Perf: Optimise Chunk#getFluid + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); +@@ -270,80 +321,89 @@ + }); + throw new ReportedException(crashreport); + } ++ */ // Paper - Perf: Optimise Chunk#getFluid + } + ++ // CraftBukkit start + @Nullable + @Override + public BlockState setBlockState(BlockPos pos, BlockState state, boolean moved) { +- int i = pos.getY(); ++ return this.setBlockState(pos, state, moved, true); ++ } ++ ++ @Nullable ++ public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) { ++ // CraftBukkit end ++ int i = blockposition.getY(); + LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i)); + boolean flag1 = chunksection.hasOnlyAir(); + +- if (flag1 && state.isAir()) { ++ if (flag1 && iblockdata.isAir()) { + return null; + } else { +- int j = pos.getX() & 15; ++ int j = blockposition.getX() & 15; + int k = i & 15; +- int l = pos.getZ() & 15; +- BlockState iblockdata1 = chunksection.setBlockState(j, k, l, state); ++ int l = blockposition.getZ() & 15; ++ BlockState iblockdata1 = chunksection.setBlockState(j, k, l, iblockdata); + +- if (iblockdata1 == state) { ++ if (iblockdata1 == iblockdata) { + return null; + } else { +- Block block = state.getBlock(); ++ Block block = iblockdata.getBlock(); + +- ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, state); +- ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, state); +- ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, state); +- ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, state); ++ ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, iblockdata); ++ ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, iblockdata); ++ ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, iblockdata); ++ ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, iblockdata); + boolean flag2 = chunksection.hasOnlyAir(); + + if (flag1 != flag2) { +- this.level.getChunkSource().getLightEngine().updateSectionStatus(pos, flag2); ++ this.level.getChunkSource().getLightEngine().updateSectionStatus(blockposition, flag2); + this.level.getChunkSource().onSectionEmptinessChanged(this.chunkPos.x, SectionPos.blockToSectionCoord(i), this.chunkPos.z, flag2); + } + +- if (LightEngine.hasDifferentLightProperties(iblockdata1, state)) { ++ if (LightEngine.hasDifferentLightProperties(iblockdata1, iblockdata)) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("updateSkyLightSources"); + this.skyLightSources.update(this, j, i, l); + gameprofilerfiller.popPush("queueCheckLight"); +- this.level.getChunkSource().getLightEngine().checkBlock(pos); ++ this.level.getChunkSource().getLightEngine().checkBlock(blockposition); + gameprofilerfiller.pop(); + } + + boolean flag3 = iblockdata1.hasBlockEntity(); + +- if (!this.level.isClientSide) { +- iblockdata1.onRemove(this.level, pos, state, moved); ++ if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ iblockdata1.onRemove(this.level, blockposition, iblockdata, flag); + } else if (!iblockdata1.is(block) && flag3) { +- this.removeBlockEntity(pos); ++ this.removeBlockEntity(blockposition); + } + + if (!chunksection.getBlockState(j, k, l).is(block)) { + return null; + } else { +- if (!this.level.isClientSide) { +- state.onPlace(this.level, pos, iblockdata1, moved); ++ // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. ++ if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { ++ iblockdata.onPlace(this.level, blockposition, iblockdata1, flag); + } + +- if (state.hasBlockEntity()) { +- BlockEntity tileentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); ++ if (iblockdata.hasBlockEntity()) { ++ BlockEntity tileentity = this.getBlockEntity(blockposition, LevelChunk.EntityCreationType.CHECK); + +- if (tileentity != null && !tileentity.isValidBlockState(state)) { +- LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{pos, tileentity.getType().builtInRegistryHolder().key().location(), state}); +- this.removeBlockEntity(pos); ++ if (tileentity != null && !tileentity.isValidBlockState(iblockdata)) { ++ LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{blockposition, tileentity.getType().builtInRegistryHolder().key().location(), iblockdata}); ++ this.removeBlockEntity(blockposition); + tileentity = null; + } + + if (tileentity == null) { +- tileentity = ((EntityBlock) block).newBlockEntity(pos, state); ++ tileentity = ((EntityBlock) block).newBlockEntity(blockposition, iblockdata); + if (tileentity != null) { + this.addAndRegisterBlockEntity(tileentity); + } + } else { +- tileentity.setBlockState(state); ++ tileentity.setBlockState(iblockdata); + this.updateBlockEntityTicker(tileentity); + } + } +@@ -375,7 +435,12 @@ + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { +- BlockEntity tileentity = (BlockEntity) this.blockEntities.get(pos); ++ // CraftBukkit start ++ BlockEntity tileentity = this.level.capturedTileEntities.get(pos); ++ if (tileentity == null) { ++ tileentity = (BlockEntity) this.blockEntities.get(pos); ++ } ++ // CraftBukkit end + + if (tileentity == null) { + CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos); +@@ -446,7 +511,13 @@ + BlockState iblockdata = this.getBlockState(blockposition); + + if (!iblockdata.hasBlockEntity()) { +- LevelChunk.LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", new Object[]{blockEntity, blockposition, iblockdata}); ++ // Paper start - ServerExceptionEvent ++ com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException( ++ "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockposition, iblockdata) ++ ); ++ e.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e); ++ // Paper end - ServerExceptionEvent + } else { + BlockState iblockdata1 = blockEntity.getBlockState(); + +@@ -500,6 +571,12 @@ + if (this.isInLevel()) { + BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos); + ++ // CraftBukkit start - SPIGOT-5561: Also remove from pending map ++ if (!this.pendingBlockEntities.isEmpty()) { ++ this.pendingBlockEntities.remove(pos); ++ } ++ // CraftBukkit end ++ + if (tileentity != null) { + Level world = this.level; + +@@ -553,6 +630,65 @@ + + } + ++ // CraftBukkit start ++ public void loadCallback() { ++ // Paper start ++ this.loadedTicketLevel = true; ++ // Paper end ++ org.bukkit.Server server = this.level.getCraftServer(); ++ this.level.getChunkSource().addLoadedChunk(this); // Paper ++ 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. ++ */ ++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); ++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); ++ ++ if (this.needsDecoration) { ++ this.needsDecoration = false; ++ java.util.Random random = new java.util.Random(); ++ random.setSeed(this.level.getSeed()); ++ long xRand = random.nextLong() / 2L * 2L + 1L; ++ long zRand = random.nextLong() / 2L * 2L + 1L; ++ random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed()); ++ ++ org.bukkit.World world = this.level.getWorld(); ++ if (world != null) { ++ this.level.populating = true; ++ try { ++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { ++ populator.populate(world, random, bukkitChunk); ++ } ++ } finally { ++ this.level.populating = false; ++ } ++ } ++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); ++ } ++ } ++ } ++ ++ public void unloadCallback() { ++ org.bukkit.Server server = this.level.getCraftServer(); ++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); ++ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); ++ server.getPluginManager().callEvent(unloadEvent); ++ // note: saving can be prevented, but not forced if no saving is actually required ++ this.mustNotSave = !unloadEvent.isSaveChunk(); ++ this.level.getChunkSource().removeLoadedChunk(this); // Paper ++ // Paper start ++ this.loadedTicketLevel = false; ++ // Paper end ++ } ++ ++ @Override ++ public boolean isUnsaved() { ++ return super.isUnsaved() && !this.mustNotSave; ++ } ++ // CraftBukkit end ++ + public boolean isEmpty() { + return false; + } +@@ -750,7 +886,7 @@ + + private void updateBlockEntityTicker(T blockEntity) { + BlockState iblockdata = blockEntity.getBlockState(); +- BlockEntityTicker blockentityticker = iblockdata.getTicker(this.level, blockEntity.getType()); ++ BlockEntityTicker blockentityticker = iblockdata.getTicker(this.level, (BlockEntityType) blockEntity.getType()); // CraftBukkit - decompile error + + if (blockentityticker == null) { + this.removeBlockEntityTicker(blockEntity.getBlockPos()); +@@ -841,7 +977,7 @@ + private boolean loggedInvalidBlockState; + + BoundTickingBlockEntity(final BlockEntity tileentity, final BlockEntityTicker blockentityticker) { +- this.blockEntity = tileentity; ++ this.blockEntity = (T) tileentity; // CraftBukkit - decompile error + this.ticker = blockentityticker; + } + +@@ -860,18 +996,25 @@ + if (this.blockEntity.getType().isValid(iblockdata)) { + this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity); + this.loggedInvalidBlockState = false; +- } else if (!this.loggedInvalidBlockState) { +- this.loggedInvalidBlockState = true; +- LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); ++ // Paper start - Remove the Block Entity if it's invalid ++ } else { ++ LevelChunk.this.removeBlockEntity(this.getPos()); ++ if (!this.loggedInvalidBlockState) { ++ this.loggedInvalidBlockState = true; ++ LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); ++ } ++ // Paper end - Remove the Block Entity if it's invalid + } + + gameprofilerfiller.pop(); + } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking block entity"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block entity being ticked"); +- +- this.blockEntity.fillCrashReportCategory(crashreportsystemdetails); +- throw new ReportedException(crashreport); ++ // Paper start - Prevent block entity and entity crashes ++ final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); ++ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); ++ net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent ++ LevelChunk.this.removeBlockEntity(this.getPos()); ++ // Paper end - Prevent block entity and entity crashes ++ // Spigot start + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch new file mode 100644 index 0000000000..f6007d4aea --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch @@ -0,0 +1,51 @@ +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -19,11 +19,11 @@ + public static final int SECTION_HEIGHT = 16; + public static final int SECTION_SIZE = 4096; + public static final int BIOME_CONTAINER_BITS = 2; +- private short nonEmptyBlockCount; ++ short nonEmptyBlockCount; // Paper - package private + private short tickingBlockCount; + private short tickingFluidCount; + public final PalettedContainer states; +- private PalettedContainerRO> biomes; ++ private PalettedContainer> biomes; // CraftBukkit - read/write + + private LevelChunkSection(LevelChunkSection section) { + this.nonEmptyBlockCount = section.nonEmptyBlockCount; +@@ -33,9 +33,9 @@ + this.biomes = section.biomes.copy(); + } + +- public LevelChunkSection(PalettedContainer blockStateContainer, PalettedContainerRO> biomeContainer) { +- this.states = blockStateContainer; +- this.biomes = biomeContainer; ++ public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { // CraftBukkit - read/write ++ this.states = datapaletteblock; ++ this.biomes = palettedcontainerro; + this.recalcBlockCounts(); + } + +@@ -49,7 +49,7 @@ + } + + public FluidState getFluidState(int x, int y, int z) { +- return ((BlockState) this.states.get(x, y, z)).getFluidState(); ++ return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid. + } + + public void acquire() { +@@ -196,6 +196,12 @@ + return (Holder) this.biomes.get(x, y, z); + } + ++ // CraftBukkit start ++ public void setBiome(int i, int j, int k, Holder biome) { ++ this.biomes.set(i, j, k, biome); ++ } ++ // CraftBukkit end ++ + public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int y, int z) { + PalettedContainer> datapaletteblock = this.biomes.recreate(); + boolean flag = true; diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch new file mode 100644 index 0000000000..aff5b81eae --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -30,14 +30,14 @@ + public final IdMap registry; + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; +- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); ++ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + + public void acquire() { +- this.threadingDetector.checkAndLock(); ++ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization + } + + public void release() { +- this.threadingDetector.checkAndUnlock(); ++ // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + + public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +@@ -110,7 +110,7 @@ + } + + @Override +- public int onResize(int newBits, T object) { ++ public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data data = this.data; + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); +@@ -135,7 +135,7 @@ + return this.getAndSet(this.strategy.getIndex(x, y, z), value); + } + +- private T getAndSet(int index, T value) { ++ private synchronized T getAndSet(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + int j = this.data.storage.getAndSet(index, i); + return this.data.palette.valueFor(j); +@@ -151,7 +151,7 @@ + } + } + +- private void set(int index, T value) { ++ private synchronized void set(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + this.data.storage.set(index, i); + } +@@ -174,7 +174,7 @@ + intSet.forEach(id -> action.accept(palette.valueFor(id))); + } + +- public void read(FriendlyByteBuf buf) { ++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -189,7 +189,7 @@ + } + + @Override +- public void write(FriendlyByteBuf buf) { ++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -237,7 +237,7 @@ + } + + @Override +- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { ++ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize + this.acquire(); + + PalettedContainerRO.PackedData var12; diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch new file mode 100644 index 0000000000..1f9c01eb79 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -81,7 +81,19 @@ + @Override + public ChunkAccess.PackedTicks getTicksForSerialization(long time) { + return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time)); ++ } ++ ++ // Paper start - If loaded util ++ @Override ++ public final FluidState getFluidIfLoaded(BlockPos blockposition) { ++ return this.getFluidState(blockposition); ++ } ++ ++ @Override ++ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) { ++ return this.getBlockState(blockposition); + } ++ // Paper end + + @Override + public BlockState getBlockState(BlockPos pos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch new file mode 100644 index 0000000000..902ea36be8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch @@ -0,0 +1,47 @@ +--- a/net/minecraft/world/level/chunk/UpgradeData.java ++++ b/net/minecraft/world/level/chunk/UpgradeData.java +@@ -113,12 +113,36 @@ + } + } + ++ // Paper start - filter out relocated neighbour ticks ++ // The lists are only supposed to contain ticks for the 1 radius neighbours of the chunk ++ private static void filterTickList(int chunkX, int chunkZ, List> ticks) { ++ for (java.util.Iterator> iterator = ticks.iterator(); iterator.hasNext();) { ++ SavedTick tick = iterator.next(); ++ BlockPos tickPos = tick.pos(); ++ int tickCX = tickPos.getX() >> 4; ++ int tickCZ = tickPos.getZ() >> 4; ++ ++ int dist = Math.max(Math.abs(chunkX - tickCX), Math.abs(chunkZ - tickCZ)); ++ ++ if (dist != 1) { ++ LOGGER.warn("Neighbour tick '" + tick + "' serialized in chunk (" + chunkX + "," + chunkZ + ") is too far (" + tickCX + "," + tickCZ + ")"); ++ iterator.remove(); ++ } ++ } ++ } ++ // Paper end - filter out relocated neighbour ticks ++ + public void upgrade(LevelChunk chunk) { + this.upgradeInside(chunk); + + for (Direction8 direction8 : DIRECTIONS) { + upgradeSides(chunk, direction8); + } ++ ++ // Paper start - filter out relocated neighbour ticks ++ filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks); ++ filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks); ++ // Paper end - filter out relocated neighbour ticks + + Level level = chunk.getLevel(); + this.neighborBlockTicks.forEach(tick -> { +@@ -129,6 +153,7 @@ + Fluid fluid = tick.type() == Fluids.EMPTY ? level.getFluidState(tick.pos()).getType() : tick.type(); + level.scheduleTick(tick.pos(), fluid, tick.delay(), tick.priority()); + }); ++ UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised + CHUNKY_FIXERS.forEach(logic -> logic.processChunk(level)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch new file mode 100644 index 0000000000..9f0303442e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch @@ -0,0 +1,84 @@ +--- a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java ++++ b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +@@ -36,7 +36,7 @@ + static CompletableFuture generateStructureStarts(WorldGenContext context, ChunkStep step, StaticCache2D chunks, ChunkAccess chunk) { + ServerLevel worldserver = context.level(); + +- if (worldserver.getServer().getWorldData().worldGenOptions().generateStructures()) { ++ if (worldserver.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit + context.generator().createStructures(worldserver.registryAccess(), worldserver.getChunkSource().getGeneratorState(), worldserver.structureManager(), chunk, context.structureManager(), worldserver.dimension()); + } + +@@ -151,7 +151,7 @@ + if (protochunk instanceof ImposterProtoChunk protochunkextension) { + chunk1 = protochunkextension.getWrapped(); + } else { +- chunk1 = new LevelChunk(worldserver, protochunk, (chunk1) -> { ++ chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix + ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities()); + }); + generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false)); +@@ -168,10 +168,61 @@ + }, context.mainThreadExecutor()); + } + +- private static void postLoadProtoChunk(ServerLevel world, List entities) { ++ public static void postLoadProtoChunk(ServerLevel world, List entities) { // Paper - public + if (!entities.isEmpty()) { +- world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD)); ++ // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities ++ world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> { ++ boolean needsRemoval = false; ++ net.minecraft.server.dedicated.DedicatedServer server = world.getCraftServer().getServer(); ++ if (!world.getChunkSource().spawnFriendlies && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) { ++ entity.discard(null); // CraftBukkit - add Bukkit remove cause ++ needsRemoval = true; ++ } ++ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving ++ return !needsRemoval; ++ })); ++ // CraftBukkit end + } + + } ++ ++ // Paper start - duplicate uuid resolving ++ // rets true if to prevent the entity from being added ++ public static boolean checkDupeUUID(ServerLevel level, net.minecraft.world.entity.Entity entity) { ++ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; ++ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { ++ return false; ++ } ++ net.minecraft.world.entity.Entity other = level.getEntity(entity.getUUID()); ++ ++ if (other == null || other == entity) { ++ return false; ++ } ++ ++ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() ++ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange ++ ) { ++ entity.discard(null); ++ return true; ++ } ++ if (!other.isRemoved()) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(java.util.UUID.randomUUID()); ++ break; ++ } ++ case DELETE: { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ return true; ++ } ++ default: ++ break; ++ } ++ } ++ return false; ++ } ++ // Paper end - duplicate uuid resolving + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch new file mode 100644 index 0000000000..b82d242d2c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch @@ -0,0 +1,158 @@ +--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -15,10 +15,16 @@ + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkPos; +-import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.chunk.ChunkGenerator; ++// CraftBukkit start ++import java.util.concurrent.ExecutionException; ++import net.minecraft.world.level.chunk.status.ChunkStatus; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler; + import net.minecraft.world.level.storage.DimensionDataStorage; + +@@ -39,27 +45,86 @@ + return this.worker.isOldChunkAround(chunkPos, checkRadius); + } + +- public CompoundTag upgradeChunkTag(ResourceKey worldKey, Supplier persistentStateManagerFactory, CompoundTag nbt, Optional>> generatorCodecKey) { +- int i = ChunkStorage.getVersion(nbt); ++ // CraftBukkit start ++ private boolean check(ServerChunkCache cps, int x, int z) { ++ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" ++ ChunkPos pos = new ChunkPos(x, z); ++ if (cps != null) { ++ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); ++ if (cps.hasChunk(x, z)) { ++ return true; ++ } ++ } + ++ CompoundTag nbt; ++ try { ++ nbt = this.read(pos).get().orElse(null); ++ } catch (InterruptedException | ExecutionException ex) { ++ throw new RuntimeException(ex); ++ } ++ if (nbt != null) { ++ CompoundTag level = nbt.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated")) { ++ return true; ++ } ++ ++ ChunkStatus status = ChunkStatus.byName(level.getString("Status")); ++ if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { ++ // CraftBukkit end ++ int i = ChunkStorage.getVersion(nbttagcompound); ++ + if (i == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { +- return nbt; ++ return nbttagcompound; + } else { + try { ++ // CraftBukkit start ++ if (i < 1466) { ++ CompoundTag level = nbttagcompound.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { ++ ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); ++ if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) { ++ level.putBoolean("LightPopulated", true); ++ } ++ } ++ } ++ // CraftBukkit end ++ + if (i < 1493) { +- nbt = DataFixTypes.CHUNK.update(this.fixerUpper, nbt, i, 1493); +- if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) { +- LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(worldKey, persistentStateManagerFactory); ++ nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493); ++ if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { ++ LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); + +- nbt = persistentstructurelegacy.updateFromLegacy(nbt); ++ nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); + } + } + +- ChunkStorage.injectDatafixingContext(nbt, worldKey, generatorCodecKey); +- nbt = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbt, Math.max(1493, i)); +- ChunkStorage.removeDatafixingContext(nbt); +- NbtUtils.addCurrentDataVersion(nbt); +- return nbt; ++ // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty ++ boolean stopBelowZero = false; ++ boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks; ++ ++ if (i <= 2730 && !belowZeroGenerationInExistingChunks) { ++ stopBelowZero = "full".equals(nbttagcompound.getCompound("Level").getString("Status")); ++ } ++ // Spigot end ++ ++ ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); ++ nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i)); ++ // Spigot start ++ if (stopBelowZero) { ++ nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString()); ++ } ++ // Spigot end ++ ChunkStorage.removeDatafixingContext(nbttagcompound); ++ NbtUtils.addCurrentDataVersion(nbttagcompound); ++ return nbttagcompound; + } catch (Exception exception) { + CrashReport crashreport = CrashReport.forThrowable(exception, "Updated chunk"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Updated chunk details"); +@@ -70,7 +135,7 @@ + } + } + +- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey worldKey, Supplier stateManagerGetter) { ++ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey worldKey, Supplier stateManagerGetter) { // CraftBukkit + LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; + + if (persistentstructurelegacy == null) { +@@ -85,7 +150,7 @@ + return persistentstructurelegacy; + } + +- public static void injectDatafixingContext(CompoundTag nbt, ResourceKey worldKey, Optional>> generatorCodecKey) { ++ public static void injectDatafixingContext(CompoundTag nbt, ResourceKey worldKey, Optional>> generatorCodecKey) { // CraftBukkit + CompoundTag nbttagcompound1 = new CompoundTag(); + + nbttagcompound1.putString("dimension", worldKey.location().toString()); +@@ -108,8 +173,19 @@ + } + + public CompletableFuture write(ChunkPos chunkPos, Supplier nbtSupplier) { ++ // Paper start - guard against possible chunk pos desync ++ final Supplier guardedPosCheck = () -> { ++ CompoundTag nbt = nbtSupplier.get(); ++ if (nbt != null && !chunkPos.equals(SerializableChunkData.getChunkCoordinate(nbt))) { ++ final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos ++ + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ return nbt; ++ }; ++ // Paper end - guard against possible chunk pos desync + this.handleLegacyStructureIndex(chunkPos); +- return this.worker.store(chunkPos, nbtSupplier); ++ return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync + } + + protected void handleLegacyStructureIndex(ChunkPos chunkPos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch new file mode 100644 index 0000000000..3955a59294 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch @@ -0,0 +1,127 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -1,3 +1,4 @@ ++// mc-dev import + package net.minecraft.world.level.chunk.storage; + + import com.google.common.annotations.VisibleForTesting; +@@ -49,7 +50,7 @@ + protected final RegionBitmap usedSectors; + + public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { +- this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync); ++ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format + } + + public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { +@@ -63,8 +64,8 @@ + } else { + this.externalFileDir = directory; + this.offsets = this.header.asIntBuffer(); +- this.offsets.limit(1024); +- this.header.position(4096); ++ ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error ++ ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error + this.timestamps = this.header.asIntBuffer(); + if (dsync) { + this.file = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.DSYNC); +@@ -73,7 +74,7 @@ + } + + this.usedSectors.force(0, 2); +- this.header.position(0); ++ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error + int i = this.file.read(this.header, 0L); + + if (i != -1) { +@@ -89,6 +90,14 @@ + if (l != 0) { + int i1 = RegionFile.getSectorNumber(l); + int j1 = RegionFile.getNumSectors(l); ++ // Spigot start ++ if (j1 == 255) { ++ // We're maxed out, so we need to read the proper length from the section ++ ByteBuffer realLen = ByteBuffer.allocate(4); ++ this.file.read(realLen, i1 * 4096); ++ j1 = (realLen.getInt(0) + 4) / 4096 + 1; ++ } ++ // Spigot end + + if (i1 < 2) { + RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1}); +@@ -128,11 +137,18 @@ + } else { + int j = RegionFile.getSectorNumber(i); + int k = RegionFile.getNumSectors(i); ++ // Spigot start ++ if (k == 255) { ++ ByteBuffer realLen = ByteBuffer.allocate(4); ++ this.file.read(realLen, j * 4096); ++ k = (realLen.getInt(0) + 4) / 4096 + 1; ++ } ++ // Spigot end + int l = k * 4096; + ByteBuffer bytebuffer = ByteBuffer.allocate(l); + + this.file.read(bytebuffer, (long) (j * 4096)); +- bytebuffer.flip(); ++ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + if (bytebuffer.remaining() < 5) { + RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); + return null; +@@ -246,7 +262,7 @@ + + try { + this.file.read(bytebuffer, (long) (j * 4096)); +- bytebuffer.flip(); ++ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + if (bytebuffer.remaining() != 5) { + return false; + } else { +@@ -280,6 +296,7 @@ + return true; + } + } catch (IOException ioexception) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper - ServerExceptionEvent + return false; + } + } +@@ -349,7 +366,7 @@ + + bytebuffer.putInt(1); + bytebuffer.put((byte) (this.version.getId() | 128)); +- bytebuffer.flip(); ++ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + return bytebuffer; + } + +@@ -358,9 +375,10 @@ + FileChannel filechannel = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + try { +- buf.position(5); ++ ((java.nio.Buffer) buf).position(5); // CraftBukkit - decompile error + filechannel.write(buf); + } catch (Throwable throwable) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper - ServerExceptionEvent + if (filechannel != null) { + try { + filechannel.close(); +@@ -382,7 +400,7 @@ + } + + private void writeHeader() throws IOException { +- this.header.position(0); ++ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error + this.file.write(this.header, 0L); + } + +@@ -418,7 +436,7 @@ + if (i != j) { + ByteBuffer bytebuffer = RegionFile.PADDING_BUFFER.duplicate(); + +- bytebuffer.position(0); ++ ((java.nio.Buffer) bytebuffer).position(0); // CraftBukkit - decompile error + this.file.write(bytebuffer, (long) (j - 1)); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch new file mode 100644 index 0000000000..4e4fd07bda --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch @@ -0,0 +1,67 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -32,21 +32,22 @@ + this.info = storageKey; + } + +- private RegionFile getRegionFile(ChunkPos pos) throws IOException { +- long i = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); ++ private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit ++ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); + RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); + + if (regionfile != null) { + return regionfile; + } else { +- if (this.regionCache.size() >= 256) { ++ if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable + ((RegionFile) this.regionCache.removeLast()).close(); + } + + FileUtil.createDirectoriesSafe(this.folder); + Path path = this.folder; +- int j = pos.getRegionX(); +- Path path1 = path.resolve("r." + j + "." + pos.getRegionZ() + ".mca"); ++ int j = chunkcoordintpair.getRegionX(); ++ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); ++ if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit + RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync); + + this.regionCache.putAndMoveToFirst(i, regionfile1); +@@ -56,7 +57,12 @@ + + @Nullable + public CompoundTag read(ChunkPos pos) throws IOException { +- RegionFile regionfile = this.getRegionFile(pos); ++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing ++ RegionFile regionfile = this.getRegionFile(pos, true); ++ if (regionfile == null) { ++ return null; ++ } ++ // CraftBukkit end + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); + + CompoundTag nbttagcompound; +@@ -96,7 +102,12 @@ + } + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { +- RegionFile regionfile = this.getRegionFile(chunkPos); ++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing ++ RegionFile regionfile = this.getRegionFile(chunkPos, true); ++ if (regionfile == null) { ++ return; ++ } ++ // CraftBukkit end + DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos); + + try { +@@ -122,7 +133,7 @@ + } + + protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { +- RegionFile regionfile = this.getRegionFile(pos); ++ RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit + + if (nbt == null) { + regionfile.clear(pos); diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch new file mode 100644 index 0000000000..65bf070d12 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +@@ -58,6 +58,15 @@ + private final RegionFileVersion.StreamWrapper inputWrapper; + private final RegionFileVersion.StreamWrapper outputWrapper; + ++ // Paper start - Configurable region compression format ++ public static RegionFileVersion getCompressionFormat() { ++ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) { ++ case GZIP -> VERSION_GZIP; ++ case ZLIB -> VERSION_DEFLATE; ++ case NONE -> VERSION_NONE; ++ }; ++ } ++ // Paper end - Configurable region compression format + private RegionFileVersion( + int id, + @Nullable String name, diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch new file mode 100644 index 0000000000..15c1561f05 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch @@ -0,0 +1,216 @@ +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -76,7 +76,8 @@ + import net.minecraft.world.ticks.SavedTick; + import org.slf4j.Logger; + +-public record SerializableChunkData(Registry biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List sectionData, List entities, List blockEntities, CompoundTag structureData) { ++// CraftBukkit - persistentDataContainer ++public record SerializableChunkData(Registry biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List sectionData, List entities, List blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) { + + public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -90,13 +91,39 @@ + public static final String SECTIONS_TAG = "sections"; + public static final String BLOCK_LIGHT_TAG = "BlockLight"; + public static final String SKY_LIGHT_TAG = "SkyLight"; ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } else { ++ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); ++ } ++ } ++ // Paper end - guard against serializing mismatching coordinates + ++ // Paper start - Do not let the server load chunks from newer versions ++ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end - Do not let the server load chunks from newer versions ++ + @Nullable + public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) { + if (!nbt.contains("Status", 8)) { + return null; + } else { +- ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); ++ // Paper start - Do not let the server load chunks from newer versions ++ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ final int dataVersion = nbt.getInt("DataVersion"); ++ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { ++ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); ++ System.exit(1); ++ } ++ } ++ // Paper end - Do not let the server load chunks from newer versions ++ ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + long i = nbt.getLong("LastUpdate"); + long j = nbt.getLong("InhabitedTime"); + ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status")); +@@ -110,7 +137,7 @@ + dataresult = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("blending_data")); + logger = SerializableChunkData.LOGGER; + Objects.requireNonNull(logger); +- blendingdata_d = (BlendingData.Packed) dataresult.resultOrPartial(logger::error).orElse((Object) null); ++ blendingdata_d = (BlendingData.Packed) ((DataResult) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error + } else { + blendingdata_d = null; + } +@@ -121,7 +148,7 @@ + dataresult = BelowZeroRetrogen.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen")); + logger = SerializableChunkData.LOGGER; + Objects.requireNonNull(logger); +- belowzeroretrogen = (BelowZeroRetrogen) dataresult.resultOrPartial(logger::error).orElse((Object) null); ++ belowzeroretrogen = (BelowZeroRetrogen) ((DataResult) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error + } else { + belowzeroretrogen = null; + } +@@ -178,7 +205,7 @@ + ListTag nbttaglist2 = nbt.getList("sections", 10); + List list4 = new ArrayList(nbttaglist2.size()); + Registry iregistry = registryManager.lookupOrThrow(Registries.BIOME); +- Codec>> codec = makeBiomeCodec(iregistry); ++ Codec>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write + + for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) { + CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1); +@@ -196,17 +223,17 @@ + datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); + } + +- Object object; ++ PalettedContainer object; // CraftBukkit - read/write + + if (nbttagcompound3.contains("biomes", 10)) { +- object = (PalettedContainerRO) codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { ++ object = codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { // CraftBukkit - read/write + logErrors(chunkcoordintpair, b0, s1); + }).getOrThrow(SerializableChunkData.ChunkReadException::new); + } else { + object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); + } + +- chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainerRO) object); ++ chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write + } else { + chunksection = null; + } +@@ -217,7 +244,8 @@ + list4.add(new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1)); + } + +- return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2); ++ // CraftBukkit - ChunkBukkitValues ++ return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2, nbt.get("ChunkBukkitValues")); + } + } + +@@ -287,7 +315,13 @@ + if (this.chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) { + protochunk.setLightEngine(levellightengine); + } ++ } ++ ++ // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading. ++ if (this.persistentDataContainer instanceof CompoundTag) { ++ ((ChunkAccess) object).persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer); + } ++ // CraftBukkit end + + ((ChunkAccess) object).setLightCorrect(this.lightCorrect); + EnumSet enumset = EnumSet.noneOf(Heightmap.Types.class); +@@ -329,6 +363,13 @@ + + while (iterator2.hasNext()) { + nbttagcompound = (CompoundTag) iterator2.next(); ++ // Paper start - do not read tile entities positioned outside the chunk ++ final BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", chunkPos, world.getWorld().getName(), blockposition); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + protochunk1.setBlockEntityNbt(nbttagcompound); + } + +@@ -348,6 +389,12 @@ + return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS)); + } + ++ // CraftBukkit start - read/write ++ private static Codec>> makeBiomeCodecRW(Registry iregistry) { ++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS)); ++ } ++ // CraftBukkit end ++ + public static SerializableChunkData copyOf(ServerLevel world, ChunkAccess chunk) { + if (!chunk.canBeSerialized()) { + throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk)); +@@ -419,7 +466,14 @@ + }); + CompoundTag nbttagcompound1 = packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()); + +- return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1); ++ // CraftBukkit start - store chunk persistent data in nbt ++ CompoundTag persistentDataContainer = null; ++ if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. ++ persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); ++ } ++ ++ return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1, persistentDataContainer); ++ // CraftBukkit end + } + } + +@@ -432,7 +486,7 @@ + nbttagcompound.putLong("LastUpdate", this.lastUpdateTime); + nbttagcompound.putLong("InhabitedTime", this.inhabitedTime); + nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString()); +- DataResult dataresult; ++ DataResult dataresult; // CraftBukkit - decompile error + Logger logger; + + if (this.blendingData != null) { +@@ -513,6 +567,11 @@ + }); + nbttagcompound.put("Heightmaps", nbttagcompound2); + nbttagcompound.put("structures", this.structureData); ++ // CraftBukkit start - store chunk persistent data in nbt ++ if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. ++ nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer); ++ } ++ // CraftBukkit end + return nbttagcompound; + } + +@@ -564,6 +623,13 @@ + chunk.setBlockEntityNbt(nbttagcompound); + } else { + BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); ++ // Paper start - do not read tile entities positioned outside the chunk ++ ChunkPos chunkPos = chunk.getPos(); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound, world.registryAccess()); + + if (tileentity != null) { +@@ -623,6 +689,12 @@ + StructureStart structurestart = StructureStart.loadStaticStart(context, nbttagcompound1.getCompound(s), worldSeed); + + if (structurestart != null) { ++ // CraftBukkit start - load persistent data for structure start ++ net.minecraft.nbt.Tag persistentBase = nbttagcompound1.getCompound(s).get("StructureBukkitValues"); ++ if (persistentBase instanceof CompoundTag) { ++ structurestart.persistentDataContainer.putAll((CompoundTag) persistentBase); ++ } ++ // CraftBukkit end + map.put(structure, structurestart); + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch new file mode 100644 index 0000000000..bc18c1f95e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch @@ -0,0 +1,57 @@ +--- a/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java ++++ b/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java +@@ -12,6 +12,9 @@ + import net.minecraft.world.level.levelgen.feature.Feature; + import net.minecraft.world.level.levelgen.feature.SpikeFeature; + import net.minecraft.world.level.levelgen.feature.configurations.SpikeConfiguration; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public enum DragonRespawnAnimation { + +@@ -27,7 +30,7 @@ + entityendercrystal.setBeamTarget(blockposition1); + } + +- fight.setRespawnStage(null.PREPARING_TO_SUMMON_PILLARS); ++ fight.setRespawnStage(PREPARING_TO_SUMMON_PILLARS); // CraftBukkit - decompile error + } + }, + PREPARING_TO_SUMMON_PILLARS { +@@ -38,7 +41,7 @@ + world.levelEvent(3001, new BlockPos(0, 128, 0), 0); + } + } else { +- fight.setRespawnStage(null.SUMMONING_PILLARS); ++ fight.setRespawnStage(SUMMONING_PILLARS); // CraftBukkit - decompile error + } + + } +@@ -81,7 +84,7 @@ + Feature.END_SPIKE.place(worldgenfeatureendspikeconfiguration, world, world.getChunkSource().getGenerator(), RandomSource.create(), new BlockPos(worldgenender_spike.getCenterX(), 45, worldgenender_spike.getCenterZ())); + } + } else if (flag1) { +- fight.setRespawnStage(null.SUMMONING_DRAGON); ++ fight.setRespawnStage(SUMMONING_DRAGON); // CraftBukkit - decompile error + } + } + +@@ -94,7 +97,7 @@ + EndCrystal entityendercrystal; + + if (tick >= 100) { +- fight.setRespawnStage(null.END); ++ fight.setRespawnStage(END); // CraftBukkit - decompile error + fight.resetSpikeCrystals(); + iterator = crystals.iterator(); + +@@ -102,7 +105,7 @@ + entityendercrystal = (EndCrystal) iterator.next(); + entityendercrystal.setBeamTarget((BlockPos) null); + world.explode(entityendercrystal, entityendercrystal.getX(), entityendercrystal.getY(), entityendercrystal.getZ(), 6.0F, Level.ExplosionInteraction.NONE); +- entityendercrystal.discard(); ++ entityendercrystal.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause + } + } else if (tick >= 80) { + world.levelEvent(3001, new BlockPos(0, 128, 0), 0); diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch new file mode 100644 index 0000000000..5514be96df --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch @@ -0,0 +1,212 @@ +--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -74,6 +74,7 @@ + private static final int GATEWAY_DISTANCE = 96; + public static final int DRAGON_SPAWN_Y = 128; + private final Predicate validPlayer; ++ private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name + public final ServerBossEvent dragonEvent; + public final ServerLevel level; + private final BlockPos origin; +@@ -102,7 +103,7 @@ + } + + public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) { +- this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); ++ this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name + this.gateways = new ObjectArrayList(); + this.ticksSinceLastPlayerScan = 21; + this.skipArenaLoadedCheck = false; +@@ -111,14 +112,20 @@ + this.origin = origin; + this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE.and(EntitySelector.withinDistance((double) origin.getX(), (double) (128 + origin.getY()), (double) origin.getZ(), 192.0D)); + this.needsStateScanning = data.needsStateScanning; +- this.dragonUUID = (UUID) data.dragonUUID.orElse((Object) null); ++ this.dragonUUID = (UUID) data.dragonUUID.orElse(null); // CraftBukkit - decompile error + this.dragonKilled = data.dragonKilled; + this.previouslyKilled = data.previouslyKilled; + if (data.isRespawning) { + this.respawnStage = DragonRespawnAnimation.START; + } ++ // Paper start - Add config to disable ender dragon legacy check ++ if (data == EndDragonFight.Data.DEFAULT && !world.paperConfig().entities.spawning.scanForLegacyEnderDragon) { ++ this.needsStateScanning = false; ++ this.dragonKilled = true; ++ } ++ // Paper end - Add config to disable ender dragon legacy check + +- this.portalLocation = (BlockPos) data.exitPortalLocation.orElse((Object) null); ++ this.portalLocation = (BlockPos) data.exitPortalLocation.orElse(null); // CraftBukkit - decompile error + this.gateways.addAll((Collection) data.gateways.orElseGet(() -> { + ObjectArrayList objectarraylist = new ObjectArrayList(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers())); + +@@ -206,9 +213,9 @@ + this.dragonUUID = entityenderdragon.getUUID(); + EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); + this.dragonKilled = false; +- if (!flag) { ++ if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon + EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); +- entityenderdragon.discard(); ++ entityenderdragon.discard(null); // CraftBukkit - add Bukkit remove cause + this.dragonUUID = null; + } + } +@@ -404,9 +411,23 @@ + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); +- if (!this.previouslyKilled) { +- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ // Paper start - Add DragonEggFormEvent ++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); ++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); ++ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - Add DragonEggFormEvent ++ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg ++ // Paper start - Add DragonEggFormEvent ++ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ } else { ++ eggEvent.setCancelled(true); + } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); ++ // Paper end - Add DragonEggFormEvent ++ } + + this.previouslyKilled = true; + this.dragonKilled = true; +@@ -419,7 +440,25 @@ + @VisibleForTesting + public void removeAllGateways() { + this.gateways.clear(); ++ } ++ ++ // Paper start - More DragonBattle API ++ public boolean spawnNewGatewayIfPossible() { ++ if (!this.gateways.isEmpty()) { ++ this.spawnNewGateway(); ++ return true; ++ } ++ return false; ++ } ++ ++ public List getSpikeCrystals() { ++ final List endCrystals = new java.util.ArrayList<>(); ++ for (final SpikeFeature.EndSpike spike : SpikeFeature.getSpikesForLevel(this.level)) { ++ endCrystals.addAll(this.level.getEntitiesOfClass(EndCrystal.class, spike.getTopBoundingBox())); ++ } ++ return endCrystals; + } ++ // Paper end - More DragonBattle API + + private void spawnNewGateway() { + if (!this.gateways.isEmpty()) { +@@ -449,6 +488,11 @@ + } + } + ++ // Paper start - Prevent "softlocked" exit portal generation ++ if (this.portalLocation.getY() <= this.level.getMinY()) { ++ this.portalLocation = this.portalLocation.atY(this.level.getMinY() + 1); ++ } ++ // Paper end - Prevent "softlocked" exit portal generation + if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) { + int i = Mth.positiveCeilDiv(4, 16); + +@@ -469,6 +513,7 @@ + entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F); + this.level.addFreshEntity(entityenderdragon); + this.dragonUUID = entityenderdragon.getUUID(); ++ this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn + } + + return entityenderdragon; +@@ -480,6 +525,10 @@ + this.ticksSinceDragonSeen = 0; + if (dragon.hasCustomName()) { + this.dragonEvent.setName(dragon.getDisplayName()); ++ // Paper start - ensure reset EnderDragon boss event name ++ } else { ++ this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME); ++ // Paper end - ensure reset EnderDragon boss event name + } + } + +@@ -513,7 +562,13 @@ + return this.previouslyKilled; + } + +- public void tryRespawn() { ++ public boolean tryRespawn() { // CraftBukkit - return boolean ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ return this.tryRespawn(null); ++ } ++ ++ public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup + if (this.dragonKilled && this.respawnStage == null) { + BlockPos blockposition = this.portalLocation; + +@@ -531,6 +586,22 @@ + blockposition = this.portalLocation; + } + ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ if (placedEndCrystalPos != null) { ++ // The end crystal must be 0 or 1 higher than the portal origin ++ int dy = placedEndCrystalPos.getY() - blockposition.getY(); ++ if (dy != 0 && dy != 1) { ++ return false; ++ } ++ // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other ++ int dx = placedEndCrystalPos.getX() - blockposition.getX(); ++ int dz = placedEndCrystalPos.getZ() - blockposition.getZ(); ++ if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) { ++ return false; ++ } ++ } ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup ++ + List list = Lists.newArrayList(); + BlockPos blockposition1 = blockposition.above(1); + Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); +@@ -540,19 +611,19 @@ + List list1 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockposition1.relative(enumdirection, 2))); + + if (list1.isEmpty()) { +- return; ++ return false; // CraftBukkit - return value + } + + list.addAll(list1); + } + + EndDragonFight.LOGGER.debug("Found all crystals, respawning dragon."); +- this.respawnDragon(list); ++ return this.respawnDragon(list); // CraftBukkit - return value + } +- ++ return false; // CraftBukkit - return value + } + +- public void respawnDragon(List crystals) { ++ public boolean respawnDragon(List list) { // CraftBukkit - return boolean + if (this.dragonKilled && this.respawnStage == null) { + for (BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection = this.findExitPortal(); shapedetector_shapedetectorcollection != null; shapedetector_shapedetectorcollection = this.findExitPortal()) { + for (int i = 0; i < this.exitPortalPattern.getWidth(); ++i) { +@@ -571,9 +642,10 @@ + this.respawnStage = DragonRespawnAnimation.START; + this.respawnTime = 0; + this.spawnExitPortal(false); +- this.respawnCrystals = crystals; ++ this.respawnCrystals = list; ++ return true; // CraftBukkit - return value + } +- ++ return false; // CraftBukkit - return value + } + + public void resetSpikeCrystals() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch new file mode 100644 index 0000000000..517de585e8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/entity/EntityAccess.java ++++ b/net/minecraft/world/level/entity/EntityAccess.java +@@ -5,6 +5,9 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.phys.AABB; ++// CraftBukkit start ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public interface EntityAccess { + +@@ -24,6 +27,12 @@ + + void setRemoved(Entity.RemovalReason reason); + ++ // CraftBukkit start - add Bukkit remove cause ++ default void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ this.setRemoved(entity_removalreason); ++ } ++ // CraftBukkit end ++ + boolean shouldBeSaved(); + + boolean isAlwaysTicking(); diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch new file mode 100644 index 0000000000..b3b9c9ccb4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/level/entity/EntityLookup.java ++++ b/net/minecraft/world/level/entity/EntityLookup.java +@@ -33,6 +33,14 @@ + UUID uUID = entity.getUUID(); + if (this.byUuid.containsKey(uUID)) { + LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity); ++ // Paper start - extra debug info ++ if (entity instanceof net.minecraft.world.entity.Entity) { ++ final T old = this.byUuid.get(entity.getUUID()); ++ if (old instanceof net.minecraft.world.entity.Entity oldCast && oldCast.getId() != entity.getId() && oldCast.valid) { ++ LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity); ++ } ++ } ++ // Paper end - extra debug info + } else { + this.byUuid.put(uUID, entity); + this.byId.put(entity.getId(), entity); diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch new file mode 100644 index 0000000000..68a70b169a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch @@ -0,0 +1,276 @@ +--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -29,8 +29,13 @@ + import net.minecraft.util.CsvOutput; + import net.minecraft.util.VisibleForDebug; + import net.minecraft.world.entity.Entity; +-import net.minecraft.world.level.ChunkPos; + import org.slf4j.Logger; ++import net.minecraft.world.level.ChunkPos; ++// CraftBukkit start ++import net.minecraft.world.level.chunk.storage.EntityStorage; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++// CraftBukkit end + + public class PersistentEntitySectionManager implements AutoCloseable { + +@@ -55,6 +60,16 @@ + this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); + } + ++ // CraftBukkit start - add method to get all entities in chunk ++ public List getEntities(ChunkPos chunkCoordIntPair) { ++ return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList()); ++ } ++ ++ public boolean isPending(long pair) { ++ return this.chunkLoadStatuses.get(pair) == ChunkLoadStatus.PENDING; ++ } ++ // CraftBukkit end ++ + void removeSectionIfEmpty(long sectionPos, EntitySection section) { + if (section.isEmpty()) { + this.sectionStorage.remove(sectionPos); +@@ -63,6 +78,7 @@ + } + + private boolean addEntityUuid(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper + if (!this.knownUuids.add(entity.getUUID())) { + PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); + return false; +@@ -76,6 +92,17 @@ + } + + private boolean addEntity(T entity, boolean existing) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper ++ // Paper start - chunk system hooks ++ // I don't want to know why this is a generic type. ++ Entity entityCasted = (Entity)entity; ++ boolean wasRemoved = entityCasted.isRemoved(); ++ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true); ++ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { ++ // removed by callback ++ return false; ++ } ++ // Paper end - chunk system hooks + if (!this.addEntityUuid(entity)) { + return false; + } else { +@@ -119,19 +146,23 @@ + } + + void startTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper + this.callbacks.onTickingStart(entity); + } + + void stopTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper + this.callbacks.onTickingEnd(entity); + } + + void startTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper + this.visibleEntityStorage.add(entity); + this.callbacks.onTrackingStart(entity); + } + + void stopTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper + this.callbacks.onTrackingEnd(entity); + this.visibleEntityStorage.remove(entity); + } +@@ -143,6 +174,7 @@ + } + + public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { ++ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper + long i = chunkPos.toLong(); + + if (trackingStatus == Visibility.HIDDEN) { +@@ -187,6 +219,7 @@ + } + + public void ensureChunkQueuedForLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper + PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); + + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { +@@ -196,33 +229,42 @@ + } + + private boolean storeChunkSections(long chunkPos, Consumer action) { +- PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); ++ // CraftBukkit start - add boolean for event call ++ return this.storeChunkSections(chunkPos, action, false); ++ } + ++ private boolean storeChunkSections(long i, Consumer consumer, boolean callEvent) { ++ // CraftBukkit end ++ PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i); ++ + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) { + return false; + } else { +- List list = (List) this.sectionStorage.getExistingSectionsInChunk(chunkPos).flatMap((entitysection) -> { ++ List list = (List) this.sectionStorage.getExistingSectionsInChunk(i).flatMap((entitysection) -> { + return entitysection.getEntities().filter(EntityAccess::shouldBeSaved); + }).collect(Collectors.toList()); + + if (list.isEmpty()) { + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) { +- this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), ImmutableList.of())); ++ if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), ImmutableList.of()); // CraftBukkit ++ this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), ImmutableList.of())); + } + + return true; + } else if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { +- this.requestChunkLoad(chunkPos); ++ this.requestChunkLoad(i); + return false; + } else { +- this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), list)); +- list.forEach(action); ++ if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit ++ this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), list)); ++ list.forEach(consumer); + return true; + } + } + } + + private void requestChunkLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper + this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); + ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); + CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); +@@ -236,9 +278,10 @@ + } + + private boolean processChunkUnload(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper + boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { + entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); +- }); ++ }, true); // CraftBukkit - add boolean for event call + + if (!flag) { + return false; +@@ -249,29 +292,35 @@ + } + + private void unloadEntity(EntityAccess entity) { +- entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK); ++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); // CraftBukkit - add Bukkit remove cause + entity.setLevelCallback(EntityInLevelCallback.NULL); + } + + private void processUnloads() { +- this.chunksToUnload.removeIf((i) -> { ++ this.chunksToUnload.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error + return this.chunkVisibility.get(i) != Visibility.HIDDEN ? true : this.processChunkUnload(i); + }); + } + + private void processPendingLoads() { +- ChunkEntities chunkentities; ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper ++ ChunkEntities chunkentities; // CraftBukkit - decompile error + + while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { + chunkentities.getEntities().forEach((entityaccess) -> { + this.addEntity(entityaccess, true); + }); + this.chunkLoadStatuses.put(chunkentities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED); ++ // CraftBukkit start - call entity load event ++ List entities = this.getEntities(chunkentities.getPos()); ++ CraftEventFactory.callEntitiesLoadEvent(((EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities); ++ // CraftBukkit end + } + + } + + public void tick() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper + this.processPendingLoads(); + this.processUnloads(); + } +@@ -292,7 +341,8 @@ + } + + public void autoSave() { +- this.getAllChunksToSave().forEach((i) -> { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper ++ this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error + boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; + + if (flag) { +@@ -306,12 +356,13 @@ + } + + public void saveAll() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper + LongSet longset = this.getAllChunksToSave(); + + while (!longset.isEmpty()) { + this.permanentStorage.flush(false); + this.processPendingLoads(); +- longset.removeIf((i) -> { ++ longset.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error + boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; + + return flag ? this.processChunkUnload(i) : this.storeChunkSections(i, (entityaccess) -> { +@@ -323,7 +374,15 @@ + } + + public void close() throws IOException { +- this.saveAll(); ++ // CraftBukkit start - add save boolean ++ this.close(true); ++ } ++ ++ public void close(boolean save) throws IOException { ++ if (save) { ++ this.saveAll(); ++ } ++ // CraftBukkit end + this.permanentStorage.close(); + } + +@@ -350,7 +409,7 @@ + public void dumpSections(Writer writer) throws IOException { + CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("visibility").addColumn("load_status").addColumn("entity_count").build(writer); + +- this.sectionStorage.getAllChunksWithExistingSections().forEach((i) -> { ++ this.sectionStorage.getAllChunksWithExistingSections().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error + PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i); + + this.sectionStorage.getExistingSectionPositionsInChunk(i).forEach((j) -> { +@@ -394,7 +453,7 @@ + private EntitySection currentSection; + + Callback(final EntityAccess entityaccess, final long i, final EntitySection entitysection) { +- this.entity = entityaccess; ++ this.entity = (T) entityaccess; // CraftBukkit - decompile error + this.currentSectionKey = i; + this.currentSection = entitysection; + } +@@ -405,6 +464,7 @@ + long i = SectionPos.asLong(blockposition); + + if (i != this.currentSectionKey) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper + Visibility visibility = this.currentSection.getStatus(); + + if (!this.currentSection.remove(this.entity)) { +@@ -459,6 +519,7 @@ + + @Override + public void onRemove(Entity.RemovalReason reason) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch new file mode 100644 index 0000000000..bcaf4b1a37 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/gameevent/DynamicGameEventListener.java ++++ b/net/minecraft/world/level/gameevent/DynamicGameEventListener.java +@@ -41,7 +41,7 @@ + + private static void ifChunkExists(LevelReader world, @Nullable SectionPos sectionPos, Consumer dispatcherConsumer) { + if (sectionPos != null) { +- ChunkAccess chunkAccess = world.getChunk(sectionPos.x(), sectionPos.z(), ChunkStatus.FULL, false); ++ ChunkAccess chunkAccess = world.getChunkIfLoadedImmediately(sectionPos.getX(), sectionPos.getZ()); // Paper - Perf: can cause sync loads while completing a chunk, resulting in deadlock + if (chunkAccess != null) { + dispatcherConsumer.accept(chunkAccess.getListenerRegistry(sectionPos.y())); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch new file mode 100644 index 0000000000..508ac18f4c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/gameevent/GameEvent.java ++++ b/net/minecraft/world/level/gameevent/GameEvent.java +@@ -85,7 +85,7 @@ + } + + private static Holder.Reference register(String id, int range) { +- return Registry.registerForHolder(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range)); ++ return io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerForHolderWithListeners(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range)); // Paper - run with listeners + } + + public static record Context(@Nullable Entity sourceEntity, @Nullable BlockState affectedState) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch new file mode 100644 index 0000000000..25f012f615 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java ++++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java +@@ -11,6 +11,12 @@ + import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.level.chunk.LevelChunk; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.CraftGameEvent; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.event.world.GenericGameEvent; ++// CraftBukkit end + + public class GameEventDispatcher { + +@@ -23,6 +29,14 @@ + public void post(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { + int i = ((GameEvent) event.value()).notificationRadius(); + BlockPos blockposition = BlockPos.containing(emitterPos); ++ // CraftBukkit start ++ GenericGameEvent event1 = new GenericGameEvent(CraftGameEvent.minecraftToBukkit(event.value()), CraftLocation.toBukkit(blockposition, this.level.getWorld()), (emitter.sourceEntity() == null) ? null : emitter.sourceEntity().getBukkitEntity(), i, !Bukkit.isPrimaryThread()); ++ this.level.getCraftServer().getPluginManager().callEvent(event1); ++ if (event1.isCancelled()) { ++ return; ++ } ++ i = event1.getRadius(); ++ // CraftBukkit end + int j = SectionPos.blockToSectionCoord(blockposition.getX() - i); + int k = SectionPos.blockToSectionCoord(blockposition.getY() - i); + int l = SectionPos.blockToSectionCoord(blockposition.getZ() - i); +@@ -42,7 +56,7 @@ + + for (int l1 = j; l1 <= i1; ++l1) { + for (int i2 = l; i2 <= k1; ++i2) { +- LevelChunk chunk = this.level.getChunkSource().getChunkNow(l1, i2); ++ LevelChunk chunk = (LevelChunk) this.level.getChunkIfLoadedImmediately(l1, i2); // Paper - Use getChunkIfLoadedImmediately + + if (chunk != null) { + for (int j2 = k; j2 <= j1; ++j2) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch new file mode 100644 index 0000000000..d96bdd56da --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java ++++ b/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java +@@ -30,6 +30,11 @@ + import net.minecraft.world.level.gameevent.PositionSource; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.craftbukkit.CraftGameEvent; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockReceiveGameEvent; ++// CraftBukkit end + + public interface VibrationSystem { + +@@ -233,7 +238,8 @@ + if (callback.requiresAdjacentChunksToBeTicking() && !Ticker.areAdjacentChunksTicking(world, blockposition1)) { + return false; + } else { +- callback.onReceiveVibration(world, blockposition, vibration.gameEvent(), (Entity) vibration.getEntity(world).orElse((Object) null), (Entity) vibration.getProjectileOwner(world).orElse((Object) null), VibrationSystem.Listener.distanceBetweenInBlocks(blockposition, blockposition1)); ++ // CraftBukkit - decompile error ++ callback.onReceiveVibration(world, blockposition, vibration.gameEvent(), (Entity) vibration.getEntity(world).orElse(null), (Entity) vibration.getProjectileOwner(world).orElse(null), VibrationSystem.Listener.distanceBetweenInBlocks(blockposition, blockposition1)); + listenerData.setCurrentVibration((VibrationInfo) null); + return true; + } +@@ -288,8 +294,14 @@ + return false; + } else { + Vec3 vec3d1 = (Vec3) optional.get(); +- +- if (!vibrationsystem_d.canReceiveVibration(world, BlockPos.containing(emitterPos), event, emitter)) { ++ // CraftBukkit start ++ boolean defaultCancel = !vibrationsystem_d.canReceiveVibration(world, BlockPos.containing(emitterPos), event, emitter); ++ Entity entity = emitter.sourceEntity(); ++ BlockReceiveGameEvent event1 = new BlockReceiveGameEvent(CraftGameEvent.minecraftToBukkit(event.value()), CraftBlock.at(world, BlockPos.containing(vec3d1)), (entity == null) ? null : entity.getBukkitEntity()); ++ event1.setCancelled(defaultCancel); ++ world.getCraftServer().getPluginManager().callEvent(event1); ++ if (event1.isCancelled()) { ++ // CraftBukkit end + return false; + } else if (Listener.isOccluded(world, emitterPos, vec3d1)) { + return false; +@@ -341,8 +353,8 @@ + public static Codec CODEC = RecordCodecBuilder.create((instance) -> { + return instance.group(VibrationInfo.CODEC.lenientOptionalFieldOf("event").forGetter((vibrationsystem_a) -> { + return Optional.ofNullable(vibrationsystem_a.currentVibration); +- }), VibrationSelector.CODEC.fieldOf("selector").forGetter(VibrationSystem.Data::getSelectionStrategy), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("event_delay").orElse(0).forGetter(VibrationSystem.Data::getTravelTimeInTicks)).apply(instance, (optional, vibrationselector, integer) -> { +- return new VibrationSystem.Data((VibrationInfo) optional.orElse((Object) null), vibrationselector, integer, true); ++ }), VibrationSelector.CODEC.optionalFieldOf("selector").xmap(o -> o.orElseGet(VibrationSelector::new), Optional::of).forGetter(VibrationSystem.Data::getSelectionStrategy), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("event_delay").orElse(0).forGetter(VibrationSystem.Data::getTravelTimeInTicks)).apply(instance, (optional, vibrationselector, integer) -> { // Paper - fix MapLike spam for missing "selector" in 1.19.2 ++ return new VibrationSystem.Data((VibrationInfo) optional.orElse(null), vibrationselector, integer, true); // CraftBukkit - decompile error + }); + }); + public static final String NBT_TAG_KEY = "listener"; diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch new file mode 100644 index 0000000000..3f778b5424 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch @@ -0,0 +1,52 @@ +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -509,6 +509,16 @@ + ); + private static final float ISLAND_THRESHOLD = -0.9F; + private final SimplexNoise islandNoise; ++ // Paper start - Perf: Optimize end generation ++ private static final class NoiseCache { ++ public long[] keys = new long[8192]; ++ public float[] values = new float[8192]; ++ public NoiseCache() { ++ java.util.Arrays.fill(keys, Long.MIN_VALUE); ++ } ++ } ++ private static final ThreadLocal> noiseCache = ThreadLocal.withInitial(java.util.WeakHashMap::new); ++ // Paper end - Perf: Optimize end generation + + public EndIslandDensityFunction(long seed) { + RandomSource randomSource = new LegacyRandomSource(seed); +@@ -521,15 +531,29 @@ + int j = z / 2; + int k = x % 2; + int l = z % 2; +- float f = 100.0F - Mth.sqrt((float)(x * x + z * z)) * 8.0F; ++ float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow + f = Mth.clamp(f, -100.0F, 80.0F); + ++ NoiseCache cache = noiseCache.get().computeIfAbsent(sampler, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation + for (int m = -12; m <= 12; m++) { + for (int n = -12; n <= 12; n++) { + long o = (long)(i + m); + long p = (long)(j + n); +- if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < -0.9F) { +- float g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F; ++ // Paper start - Perf: Optimize end generation by using a noise cache ++ long key = net.minecraft.world.level.ChunkPos.asLong((int) o, (int) p); ++ int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191; ++ float g = Float.MIN_VALUE; ++ if (cache.keys[index] == key) { ++ g = cache.values[index]; ++ } else { ++ if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < -0.9F) { ++ g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F; ++ } ++ cache.keys[index] = key; ++ cache.values[index] = g; ++ } ++ if (g != Float.MIN_VALUE) { ++ // Paper end - Perf: Optimize end generation + float h = (float)(k - m * 2); + float q = (float)(l - n * 2); + float r = 100.0F - Mth.sqrt(h * h + q * q) * g; diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch new file mode 100644 index 0000000000..da8a1b29d5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch @@ -0,0 +1,38 @@ +--- a/net/minecraft/world/level/levelgen/FlatLevelSource.java ++++ b/net/minecraft/world/level/levelgen/FlatLevelSource.java +@@ -34,22 +34,28 @@ + private final FlatLevelGeneratorSettings settings; + + public FlatLevelSource(FlatLevelGeneratorSettings config) { +- FixedBiomeSource worldchunkmanagerhell = new FixedBiomeSource(config.getBiome()); ++ // CraftBukkit start ++ // WorldChunkManagerHell worldchunkmanagerhell = new WorldChunkManagerHell(generatorsettingsflat.getBiome()); + +- Objects.requireNonNull(config); +- super(worldchunkmanagerhell, Util.memoize(config::adjustGenerationSettings)); +- this.settings = config; ++ // Objects.requireNonNull(generatorsettingsflat); ++ this(config, new FixedBiomeSource(config.getBiome())); + } + ++ public FlatLevelSource(FlatLevelGeneratorSettings generatorsettingsflat, net.minecraft.world.level.biome.BiomeSource worldchunkmanager) { ++ super(worldchunkmanager, Util.memoize(generatorsettingsflat::adjustGenerationSettings)); ++ // CraftBukkit end ++ this.settings = generatorsettingsflat; ++ } ++ + @Override +- public ChunkGeneratorStructureState createState(HolderLookup structureSetRegistry, RandomState noiseConfig, long seed) { ++ public ChunkGeneratorStructureState createState(HolderLookup holderlookup, RandomState randomstate, long i, org.spigotmc.SpigotWorldConfig conf) { // Spigot + Stream> stream = (Stream) this.settings.structureOverrides().map(HolderSet::stream).orElseGet(() -> { +- return structureSetRegistry.listElements().map((holder_c) -> { ++ return holderlookup.listElements().map((holder_c) -> { + return holder_c; + }); + }); + +- return ChunkGeneratorStructureState.createForFlat(noiseConfig, seed, this.biomeSource, stream); ++ return ChunkGeneratorStructureState.createForFlat(randomstate, i, this.biomeSource, stream, conf); // Spigot + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch new file mode 100644 index 0000000000..e3e53c8dde --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch @@ -0,0 +1,7 @@ +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -1,3 +1,4 @@ ++// keep + package net.minecraft.world.level.levelgen; + + import com.google.common.annotations.VisibleForTesting; diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch new file mode 100644 index 0000000000..9d044b3016 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch @@ -0,0 +1,79 @@ +--- a/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -24,6 +24,7 @@ + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { +@@ -31,23 +32,51 @@ + } else { + RandomSource randomsource = world.random; + +- --this.nextTick; +- if (this.nextTick > 0) { ++ // Paper start - Pillager patrol spawn settings and per player options ++ // Random player selection moved up for per player spawning and configuration ++ int j = world.players().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.nextTick += 12000 + randomsource.nextInt(1200); +- long i = world.getDayTime() / 24000L; ++ this.nextTick--; ++ patrolSpawnDelay = this.nextTick; ++ } + +- if (i >= 5L && world.isDay()) { +- if (randomsource.nextInt(5) != 0) { ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { ++ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = world.getDayTime() / 24000L; ++ } ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } else { ++ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } ++ ++ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { ++ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { ++ // Paper end - Pillager patrol spawn settings and per player options + return 0; + } else { +- int j = world.players().size(); + + if (j < 1) { + return 0; + } else { +- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; +@@ -116,7 +145,7 @@ + + entitymonsterpatrolling.setPos((double) pos.getX(), (double) pos.getY(), (double) pos.getZ()); + entitymonsterpatrolling.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.PATROL, (SpawnGroupData) null); +- world.addFreshEntityWithPassengers(entitymonsterpatrolling); ++ world.addFreshEntityWithPassengers(entitymonsterpatrolling, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PATROL); // CraftBukkit + return true; + } else { + return false; diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch new file mode 100644 index 0000000000..e3cf03e4f8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch @@ -0,0 +1,68 @@ +--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -32,13 +32,22 @@ + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DOINSOMNIA)) { + return 0; + } else { ++ // Paper start - Ability to control player's insomnia and phantoms ++ if (world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) { ++ return 0; ++ } ++ // Paper end - Ability to control player's insomnia and phantoms + RandomSource randomsource = world.random; + + --this.nextTick; + if (this.nextTick > 0) { + return 0; + } else { +- this.nextTick += (60 + randomsource.nextInt(60)) * 20; ++ // Paper start - Ability to control player's insomnia and phantoms ++ int spawnAttemptMinSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds; ++ int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; ++ this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; ++ // Paper end - Ability to control player's insomnia and phantoms + if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { + return 0; + } else { +@@ -48,7 +57,7 @@ + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!entityplayer.isSpectator()) { ++ if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls + BlockPos blockposition = entityplayer.blockPosition(); + + if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { +@@ -59,7 +68,7 @@ + int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); + boolean flag2 = true; + +- if (randomsource.nextInt(j) >= 72000) { ++ if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms + BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21)); + BlockState iblockdata = world.getBlockState(blockposition1); + FluidState fluid = world.getFluidState(blockposition1); +@@ -69,12 +78,22 @@ + int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + + for (int l = 0; l < k; ++l) { ++ // Paper start - PhantomPreSpawnEvent ++ com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(io.papermc.paper.util.MCUtil.toLocation(world, blockposition1), entityplayer.getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ // Paper end - PhantomPreSpawnEvent + Phantom entityphantom = (Phantom) EntityType.PHANTOM.create(world, EntitySpawnReason.NATURAL); + + if (entityphantom != null) { ++ entityphantom.setSpawningEntity(entityplayer.getUUID()); // Paper - PhantomPreSpawnEvent + entityphantom.moveTo(blockposition1, 0.0F, 0.0F); + groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, EntitySpawnReason.NATURAL, groupdataentity); +- world.addFreshEntityWithPassengers(entityphantom); ++ world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit + ++i; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch new file mode 100644 index 0000000000..d2cdde6d47 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch @@ -0,0 +1,72 @@ +--- a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java +@@ -7,6 +7,11 @@ + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; ++// CraftBukkit start ++import java.util.List; ++import org.bukkit.block.BlockState; ++import org.bukkit.event.world.PortalCreateEvent; ++// CraftBukkit end + + public class EndPlatformFeature extends Feature { + +@@ -21,24 +26,51 @@ + } + + public static void createEndPlatform(ServerLevelAccessor world, BlockPos pos, boolean breakBlocks) { +- BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); ++ EndPlatformFeature.createEndPlatform(world, pos, breakBlocks, null); ++ // CraftBukkit start ++ } + ++ public static void createEndPlatform(ServerLevelAccessor worldaccess, BlockPos blockposition, boolean flag, Entity entity) { ++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(worldaccess); ++ // CraftBukkit end ++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); ++ + for (int i = -2; i <= 2; ++i) { + for (int j = -2; j <= 2; ++j) { + for (int k = -1; k < 3; ++k) { +- BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(pos).move(j, k, i); ++ BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(blockposition).move(j, k, i); + Block block = k == -1 ? Blocks.OBSIDIAN : Blocks.AIR; + +- if (!world.getBlockState(blockposition_mutableblockposition1).is(block)) { +- if (breakBlocks) { +- world.destroyBlock(blockposition_mutableblockposition1, true, (Entity) null); ++ // CraftBukkit start ++ if (!blockList.getBlockState(blockposition_mutableblockposition1).is(block)) { ++ if (flag) { ++ blockList.destroyBlock(blockposition_mutableblockposition1, true, (Entity) null); + } + +- world.setBlock(blockposition_mutableblockposition1, block.defaultBlockState(), 3); ++ blockList.setBlock(blockposition_mutableblockposition1, block.defaultBlockState(), 3); ++ // CraftBukkit end + } + } + } + } ++ // CraftBukkit start ++ // SPIGOT-7746: Entity will only be null during world generation, which is async, so just generate without event ++ if (entity != null) { ++ org.bukkit.World bworld = worldaccess.getLevel().getWorld(); ++ PortalCreateEvent portalEvent = new PortalCreateEvent((List) (List) blockList.getList(), bworld, entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM); + ++ worldaccess.getLevel().getCraftServer().getPluginManager().callEvent(portalEvent); ++ if (portalEvent.isCancelled()) { ++ return; ++ } ++ } ++ ++ // SPIGOT-7856: End platform not dropping items after replacing blocks ++ if (flag) { ++ blockList.getList().forEach((state) -> worldaccess.destroyBlock(state.getPosition(), true, null)); ++ } ++ blockList.updateList(); ++ // CraftBukkit end ++ + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch new file mode 100644 index 0000000000..3db119d7ed --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/levelgen/feature/SpikeFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +@@ -115,6 +115,7 @@ + endCrystal.moveTo( + (double)spike.getCenterX() + 0.5, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F + ); ++ endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals + world.addFreshEntity(endCrystal); + BlockPos blockPos2 = endCrystal.blockPosition(); + this.setBlock(world, blockPos2.below(), Blocks.BEDROCK.defaultBlockState()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch new file mode 100644 index 0000000000..4df0e3925c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java ++++ b/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java +@@ -26,6 +26,7 @@ + + @Override + public void place(TreeDecorator.Context generator) { ++ if (generator.logs().isEmpty()) return; // Paper - Fix crash when trying to generate without logs + RandomSource randomSource = generator.random(); + if (!(randomSource.nextFloat() >= this.probability)) { + List list = generator.logs(); diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch new file mode 100644 index 0000000000..04607e1797 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch @@ -0,0 +1,32 @@ +--- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java ++++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java +@@ -18,7 +18,7 @@ + import net.minecraft.resources.ResourceKey; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkPos; +-import net.minecraft.world.level.Level; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.storage.DimensionDataStorage; + + public class LegacyStructureDataHandler { +@@ -233,16 +233,16 @@ + } + } + +- public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey world, @Nullable DimensionDataStorage persistentStateManager) { +- if (world == Level.OVERWORLD) { ++ public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey world, @Nullable DimensionDataStorage persistentStateManager) { // CraftBukkit ++ if (world == LevelStem.OVERWORLD) { // CraftBukkit + return new LegacyStructureDataHandler(persistentStateManager, ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"), ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument")); + } else { + ImmutableList immutablelist; + +- if (world == Level.NETHER) { ++ if (world == LevelStem.NETHER) { // CraftBukkit + immutablelist = ImmutableList.of("Fortress"); + return new LegacyStructureDataHandler(persistentStateManager, immutablelist, immutablelist); +- } else if (world == Level.END) { ++ } else if (world == LevelStem.END) { // CraftBukkit + immutablelist = ImmutableList.of("EndCity"); + return new LegacyStructureDataHandler(persistentStateManager, immutablelist, immutablelist); + } else { diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch new file mode 100644 index 0000000000..9e6cb9ad46 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch @@ -0,0 +1,50 @@ +--- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -40,7 +40,7 @@ + private final ChunkScanAccess storageAccess; + private final RegistryAccess registryAccess; + private final StructureTemplateManager structureTemplateManager; +- private final ResourceKey dimension; ++ private final ResourceKey dimension; // Paper - fix missing CB diff + private final ChunkGenerator chunkGenerator; + private final RandomState randomState; + private final LevelHeightAccessor heightAccessor; +@@ -54,7 +54,7 @@ + ChunkScanAccess chunkIoWorker, + RegistryAccess registryManager, + StructureTemplateManager structureTemplateManager, +- ResourceKey worldKey, ++ ResourceKey worldKey, // Paper - fix missing CB diff + ChunkGenerator chunkGenerator, + RandomState noiseConfig, + LevelHeightAccessor world, +@@ -74,6 +74,20 @@ + this.fixerUpper = dataFixer; + } + ++ // Paper start - add missing structure salt configs ++ @Nullable ++ private Integer getSaltOverride(Structure type) { ++ if (this.heightAccessor instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ if (type instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure) { ++ return serverLevel.spigotConfig.mineshaftSeed; ++ } else if (type instanceof net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure) { ++ return serverLevel.spigotConfig.buriedTreasureSeed; ++ } ++ } ++ return null; ++ } ++ // Paper end - add missing structure seed configs ++ + public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) { + long l = pos.toLong(); + Object2IntMap object2IntMap = this.loadedChunks.get(l); +@@ -83,7 +97,7 @@ + StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l); + if (structureCheckResult != null) { + return structureCheckResult; +- } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) { ++ } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs + return StructureCheckResult.START_NOT_PRESENT; + } else { + boolean bl = this.featureChecks diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch new file mode 100644 index 0000000000..918d4c34ed --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch @@ -0,0 +1,164 @@ +--- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java ++++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java +@@ -29,8 +29,6 @@ + import net.minecraft.world.level.block.Mirror; + import net.minecraft.world.level.block.Rotation; + import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.ChestBlockEntity; +-import net.minecraft.world.level.block.entity.DispenserBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.Heightmap; +@@ -51,7 +49,7 @@ + private Rotation rotation; + protected int genDepth; + private final StructurePieceType type; +- private static final Set SHAPE_CHECK_BLOCKS = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.PALE_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); ++ public static final Set SHAPE_CHECK_BLOCKS = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.PALE_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); // CraftBukkit - decompile error / PAIL private -> public + + protected StructurePiece(StructurePieceType type, int length, BoundingBox boundingBox) { + this.type = type; +@@ -80,13 +78,11 @@ + CompoundTag nbttagcompound = new CompoundTag(); + + nbttagcompound.putString("id", BuiltInRegistries.STRUCTURE_PIECE.getKey(this.getType()).toString()); +- DataResult dataresult = BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox); +- Logger logger = StructurePiece.LOGGER; +- +- Objects.requireNonNull(logger); +- dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { +- nbttagcompound.put("BB", nbtbase); ++ // CraftBukkit start - decompile error ++ BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox).resultOrPartial(Objects.requireNonNull(StructurePiece.LOGGER)::error).ifPresent((nbtbase) -> { ++ nbttagcompound.put("BB", nbtbase); + }); ++ // CraftBukkit end + Direction enumdirection = this.getOrientation(); + + nbttagcompound.putInt("O", enumdirection == null ? -1 : enumdirection.get2DDataValue()); +@@ -186,6 +182,11 @@ + } + + world.setBlock(blockposition_mutableblockposition, block, 2); ++ // CraftBukkit start - fluid handling is already done if we have a transformer generator access ++ if (world instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess) { ++ return; ++ } ++ // CraftBukkit end + FluidState fluid = world.getFluidState(blockposition_mutableblockposition); + + if (!fluid.isEmpty()) { +@@ -195,10 +196,42 @@ + if (StructurePiece.SHAPE_CHECK_BLOCKS.contains(block.getBlock())) { + world.getChunk(blockposition_mutableblockposition).markPosForPostprocessing(blockposition_mutableblockposition); + } ++ ++ } ++ } ++ } ++ ++ // CraftBukkit start ++ protected boolean placeCraftBlockEntity(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.craftbukkit.block.CraftBlockEntityState craftBlockEntityState, int i) { ++ if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) { ++ return transformerAccess.setCraftBlock(position, craftBlockEntityState, i); ++ } ++ boolean result = worldAccess.setBlock(position, craftBlockEntityState.getHandle(), i); ++ BlockEntity tileEntity = worldAccess.getBlockEntity(position); ++ if (tileEntity != null) { ++ tileEntity.loadWithComponents(craftBlockEntityState.getSnapshotNBT(), worldAccess.registryAccess()); ++ } ++ return result; ++ } + ++ protected void placeCraftSpawner(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.entity.EntityType entityType, int i) { ++ // This method is used in structures that are generated by code and place spawners as they set the entity after the block was placed making it impossible for plugins to access that information ++ org.bukkit.craftbukkit.block.CraftCreatureSpawner spawner = (org.bukkit.craftbukkit.block.CraftCreatureSpawner) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, Blocks.SPAWNER.defaultBlockState(), null); ++ spawner.setSpawnedType(entityType); ++ this.placeCraftBlockEntity(worldAccess, position, spawner, i); ++ } ++ ++ protected void setCraftLootTable(ServerLevelAccessor worldAccess, BlockPos position, RandomSource randomSource, ResourceKey loottableKey) { ++ // This method is used in structures that use data markers to a loot table to loot containers as otherwise plugins won't have access to that information. ++ net.minecraft.world.level.block.entity.BlockEntity tileEntity = worldAccess.getBlockEntity(position); ++ if (tileEntity instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity tileEntityLootable) { ++ tileEntityLootable.setLootTable(loottableKey, randomSource.nextLong()); ++ if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) { ++ transformerAccess.setCraftBlock(position, (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, tileEntity.getBlockState(), tileEntityLootable.saveWithFullMetadata(worldAccess.registryAccess())), 3); + } + } + } ++ // CraftBukkit end + + protected boolean canBeReplaced(LevelReader world, int x, int y, int z, BoundingBox box) { + return true; +@@ -393,12 +426,20 @@ + block = StructurePiece.reorient(world, pos, Blocks.CHEST.defaultBlockState()); + } + +- world.setBlock(pos, block, 2); +- BlockEntity tileentity = world.getBlockEntity(pos); ++ // CraftBukkit start ++ /* ++ worldaccess.setBlock(blockposition, iblockdata, 2); ++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition); + +- if (tileentity instanceof ChestBlockEntity) { +- ((ChestBlockEntity) tileentity).setLootTable(lootTable, random.nextLong()); ++ if (tileentity instanceof TileEntityChest) { ++ ((TileEntityChest) tileentity).setLootTable(resourcekey, randomsource.nextLong()); + } ++ */ ++ org.bukkit.craftbukkit.block.CraftChest chestState = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, block, null); ++ chestState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable)); ++ chestState.setSeed(random.nextLong()); ++ this.placeCraftBlockEntity(world, pos, chestState, 2); ++ // CraftBukkit end + + return true; + } else { +@@ -410,13 +451,32 @@ + BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getWorldPos(x, y, z); + + if (boundingBox.isInside(blockposition_mutableblockposition) && !world.getBlockState(blockposition_mutableblockposition).is(Blocks.DISPENSER)) { +- this.placeBlock(world, (BlockState) Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, boundingBox); +- BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition); ++ // CraftBukkit start ++ /* ++ this.placeBlock(generatoraccessseed, (IBlockData) Blocks.DISPENSER.defaultBlockState().setValue(BlockDispenser.FACING, enumdirection), i, j, k, structureboundingbox); ++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition); + +- if (tileentity instanceof DispenserBlockEntity) { +- ((DispenserBlockEntity) tileentity).setLootTable(lootTable, random.nextLong()); ++ if (tileentity instanceof TileEntityDispenser) { ++ ((TileEntityDispenser) tileentity).setLootTable(resourcekey, randomsource.nextLong()); + } ++ */ ++ if (!this.canBeReplaced(world, x, y, z, boundingBox)) { ++ return true; ++ } ++ BlockState iblockdata = Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing); ++ if (this.mirror != Mirror.NONE) { ++ iblockdata = iblockdata.mirror(this.mirror); ++ } ++ if (this.rotation != Rotation.NONE) { ++ iblockdata = iblockdata.rotate(this.rotation); ++ } + ++ org.bukkit.craftbukkit.block.CraftDispenser dispenserState = (org.bukkit.craftbukkit.block.CraftDispenser) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition_mutableblockposition, iblockdata, null); ++ dispenserState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable)); ++ dispenserState.setSeed(random.nextLong()); ++ this.placeCraftBlockEntity(world, blockposition_mutableblockposition, dispenserState, 2); ++ // CraftBukkit end ++ + return true; + } else { + return false; +@@ -428,7 +488,7 @@ + } + + public static BoundingBox createBoundingBox(Stream pieces) { +- Stream stream1 = pieces.map(StructurePiece::getBoundingBox); ++ Stream stream1 = pieces.map(StructurePiece::getBoundingBox); // CraftBukkit - decompile error + + Objects.requireNonNull(stream1); + return (BoundingBox) BoundingBox.encapsulatingBoxes(stream1::iterator).orElseThrow(() -> { diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch new file mode 100644 index 0000000000..83539110a1 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch @@ -0,0 +1,59 @@ +--- a/net/minecraft/world/level/levelgen/structure/StructureStart.java ++++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java +@@ -32,6 +32,12 @@ + @Nullable + private volatile BoundingBox cachedBoundingBox; + ++ // CraftBukkit start ++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); ++ public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(StructureStart.DATA_TYPE_REGISTRY); ++ public org.bukkit.event.world.AsyncStructureGenerateEvent.Cause generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.WORLD_GENERATION; ++ // CraftBukkit end ++ + public StructureStart(Structure structure, ChunkPos pos, int references, PiecesContainer children) { + this.structure = structure; + this.chunkPos = pos; +@@ -91,15 +97,29 @@ + BoundingBox structureboundingbox1 = ((StructurePiece) list.get(0)).boundingBox; + BlockPos blockposition = structureboundingbox1.getCenter(); + BlockPos blockposition1 = new BlockPos(blockposition.getX(), structureboundingbox1.minY(), blockposition.getZ()); ++ // CraftBukkit start ++ /* + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + StructurePiece structurepiece = (StructurePiece) iterator.next(); + +- if (structurepiece.getBoundingBox().intersects(chunkBox)) { +- structurepiece.postProcess(world, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, blockposition1); ++ if (structurepiece.getBoundingBox().intersects(structureboundingbox)) { ++ structurepiece.postProcess(generatoraccessseed, structuremanager, chunkgenerator, randomsource, structureboundingbox, chunkcoordintpair, blockposition1); + } + } ++ */ ++ List pieces = list.stream().filter(piece -> piece.getBoundingBox().intersects(chunkBox)).toList(); ++ if (!pieces.isEmpty()) { ++ org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess = new org.bukkit.craftbukkit.util.TransformerGeneratorAccess(); ++ transformerAccess.setHandle(world); ++ transformerAccess.setStructureTransformer(new org.bukkit.craftbukkit.util.CraftStructureTransformer(this.generationEventCause, world, structureAccessor, this.structure, chunkBox, chunkPos)); ++ for (StructurePiece piece : pieces) { ++ piece.postProcess(transformerAccess, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, blockposition1); ++ } ++ transformerAccess.getStructureTransformer().discard(); ++ } ++ // CraftBukkit end + + this.structure.afterPlace(world, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, this.pieceContainer); + } +@@ -107,6 +127,11 @@ + + public CompoundTag createTag(StructurePieceSerializationContext context, ChunkPos chunkPos) { + CompoundTag nbttagcompound = new CompoundTag(); ++ // CraftBukkit start - store persistent data in nbt ++ if (!this.persistentDataContainer.isEmpty()) { ++ nbttagcompound.put("StructureBukkitValues", this.persistentDataContainer.toTagCompound()); ++ } ++ // CraftBukkit end + + if (this.isValid()) { + nbttagcompound.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch new file mode 100644 index 0000000000..8abda3baff --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch @@ -0,0 +1,93 @@ +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -79,14 +79,30 @@ + return this.exclusionZone; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs + public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) { ++ // Paper start - Add missing structure set seed configs ++ return this.isStructureChunk(calculator, chunkX, chunkZ, null); ++ } ++ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey structureSetKey) { ++ Integer saltOverride = null; ++ if (structureSetKey != null) { ++ if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) { ++ saltOverride = calculator.conf.mineshaftSeed; ++ } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) { ++ saltOverride = calculator.conf.buriedTreasureSeed; ++ } ++ } ++ // Paper end - Add missing structure set seed configs + return this.isPlacementChunk(calculator, chunkX, chunkZ) +- && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed()) ++ && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs + && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ); + } + +- public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) { +- return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency); ++ // Paper start - Add missing structure set seed configs ++ public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) { ++ return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride); ++ // Paper end - Add missing structure set seed configs + } + + public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) { +@@ -101,25 +117,31 @@ + + public abstract StructurePlacementType type(); + +- private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); + worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ if (saltOverride == null) { // Paper - Add missing structure set seed configs + worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ); ++ // Paper start - Add missing structure set seed configs ++ } else { ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride); ++ } ++ // Paper end - Add missing structure set seed configs + return worldgenRandom.nextDouble() < (double)frequency; + } + +- private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320); ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + int i = chunkX >> 4; + int j = chunkZ >> 4; + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +@@ -147,7 +169,7 @@ + + @FunctionalInterface + public interface FrequencyReducer { +- boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance); ++ boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs + } + + public static enum FrequencyReductionMethod implements StringRepresentable { +@@ -167,8 +189,8 @@ + this.reducer = generationPredicate; + } + +- public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) { +- return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance); ++ public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs ++ return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch new file mode 100644 index 0000000000..84696b809f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java +@@ -68,6 +68,15 @@ + + private static void placeSuspiciousSand(BoundingBox box, WorldGenLevel world, BlockPos pos) { + if (box.isInside(pos)) { ++ // CraftBukkit start ++ if (world instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) { ++ org.bukkit.craftbukkit.block.CraftBrushableBlock brushableState = (org.bukkit.craftbukkit.block.CraftBrushableBlock) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), null); ++ brushableState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY)); ++ brushableState.setSeed(pos.asLong()); ++ transformerAccess.setCraftBlock(pos, brushableState, 2); ++ return; ++ } ++ // CraftBukkit end + world.setBlock(pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), 2); + world.getBlockEntity(pos, BlockEntityType.BRUSHABLE_BLOCK).ifPresent((brushableblockentity) -> { + brushableblockentity.setLootTable(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY, pos.asLong()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch new file mode 100644 index 0000000000..0ec21beb4f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java +@@ -285,7 +285,12 @@ + BlockPos blockposition1 = pos.below(); + + if (boundingBox.isInside(blockposition1)) { +- RandomizableContainer.setBlockEntityLootTable(world, random, blockposition1, BuiltInLootTables.END_CITY_TREASURE); ++ // CraftBukkit start - ensure block transformation ++ /* ++ RandomizableContainer.setBlockEntityLootTable(worldaccess, randomsource, blockposition1, LootTables.END_CITY_TREASURE); ++ */ ++ this.setCraftLootTable(world, blockposition1, random, BuiltInLootTables.END_CITY_TREASURE); ++ // CraftBukkit end + } + } else if (boundingBox.isInside(pos) && Level.isInSpawnableBounds(pos)) { + if (metadata.startsWith("Sentry")) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch new file mode 100644 index 0000000000..5dc0652fc4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java +@@ -14,8 +14,6 @@ + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.Mirror; + import net.minecraft.world.level.block.Rotation; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.ChestBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.Heightmap; +@@ -86,11 +84,16 @@ + protected void handleDataMarker(String metadata, BlockPos pos, ServerLevelAccessor world, RandomSource random, BoundingBox boundingBox) { + if ("chest".equals(metadata)) { + world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); +- BlockEntity tileentity = world.getBlockEntity(pos.below()); ++ // CraftBukkit start - ensure block transformation ++ /* ++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition.below()); + +- if (tileentity instanceof ChestBlockEntity) { +- ((ChestBlockEntity) tileentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, random.nextLong()); ++ if (tileentity instanceof TileEntityChest) { ++ ((TileEntityChest) tileentity).setLootTable(LootTables.IGLOO_CHEST, randomsource.nextLong()); + } ++ */ ++ this.setCraftLootTable(world, pos.below(), random, BuiltInLootTables.IGLOO_CHEST); ++ // CraftBukkit end + + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch new file mode 100644 index 0000000000..32925fee48 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch @@ -0,0 +1,68 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java +@@ -12,6 +12,7 @@ + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; + import net.minecraft.resources.ResourceKey; + import net.minecraft.tags.BiomeTags; + import net.minecraft.util.RandomSource; +@@ -30,8 +31,6 @@ + import net.minecraft.world.level.block.FenceBlock; + import net.minecraft.world.level.block.RailBlock; + import net.minecraft.world.level.block.WallTorchBlock; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.RailShape; + import net.minecraft.world.level.chunk.ChunkGenerator; +@@ -520,14 +519,19 @@ + + if (chunkBox.isInside(blockposition_mutableblockposition) && this.isInterior(world, 1, 0, l, chunkBox)) { + this.hasPlacedSpider = true; +- world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); +- BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition); +- +- if (tileentity instanceof SpawnerBlockEntity) { +- SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity; ++ // CraftBukkit start ++ /* ++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); ++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition); + +- tileentitymobspawner.setEntityId(EntityType.CAVE_SPIDER, random); ++ if (tileentity instanceof TileEntityMobSpawner) { ++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity; ++ ++ tileentitymobspawner.setEntityId(EntityTypes.CAVE_SPIDER, randomsource); + } ++ */ ++ this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.CAVE_SPIDER, 2); ++ // CraftBukkit end + } + } + } +@@ -819,11 +823,11 @@ + + public MineShaftRoom(CompoundTag nbt) { + super(StructurePieceType.MINE_SHAFT_ROOM, nbt); +- DataResult dataresult = BoundingBox.CODEC.listOf().parse(NbtOps.INSTANCE, nbt.getList("Entrances", 11)); ++ DataResult> dataresult = BoundingBox.CODEC.listOf().parse(NbtOps.INSTANCE, nbt.getList("Entrances", 11)); // CraftBukkit - decompile error + Logger logger = MineshaftPieces.LOGGER; + + Objects.requireNonNull(logger); +- Optional optional = dataresult.resultOrPartial(logger::error); ++ Optional> optional = dataresult.resultOrPartial(logger::error); // CraftBukkit - decompile error + List list = this.childEntranceBoxes; + + Objects.requireNonNull(this.childEntranceBoxes); +@@ -929,7 +933,7 @@ + @Override + protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag nbt) { + super.addAdditionalSaveData(context, nbt); +- DataResult dataresult = BoundingBox.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.childEntranceBoxes); ++ DataResult dataresult = BoundingBox.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.childEntranceBoxes); // CraftBukkit - decompile error + Logger logger = MineshaftPieces.LOGGER; + + Objects.requireNonNull(logger); diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch new file mode 100644 index 0000000000..23051dcaf5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java +@@ -8,15 +8,12 @@ + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.util.RandomSource; +-import net.minecraft.world.entity.EntityType; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.StructureManager; + import net.minecraft.world.level.WorldGenLevel; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.FenceBlock; + import net.minecraft.world.level.block.StairBlock; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.structure.BoundingBox; +@@ -428,14 +425,19 @@ + + if (chunkBox.isInside(blockposition_mutableblockposition)) { + this.hasPlacedSpawner = true; +- world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); +- BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition); +- +- if (tileentity instanceof SpawnerBlockEntity) { +- SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity; +- +- tileentitymobspawner.setEntityId(EntityType.BLAZE, random); ++ // CraftBukkit start ++ /* ++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); ++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition); ++ ++ if (tileentity instanceof TileEntityMobSpawner) { ++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity; ++ ++ tileentitymobspawner.setEntityId(EntityTypes.BLAZE, randomsource); + } ++ */ ++ this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.BLAZE, 2); ++ // CraftBukkit end + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch new file mode 100644 index 0000000000..7e1e399a8b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java +@@ -27,8 +27,6 @@ + import net.minecraft.world.level.block.ChestBlock; + import net.minecraft.world.level.block.Mirror; + import net.minecraft.world.level.block.Rotation; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.ChestBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.Heightmap; +@@ -200,12 +198,20 @@ + @Override + protected void handleDataMarker(String metadata, BlockPos pos, ServerLevelAccessor world, RandomSource random, BoundingBox boundingBox) { + if ("chest".equals(metadata)) { +- world.setBlock(pos, (BlockState) Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, world.getFluidState(pos).is(FluidTags.WATER)), 2); +- BlockEntity tileentity = world.getBlockEntity(pos); +- +- if (tileentity instanceof ChestBlockEntity) { +- ((ChestBlockEntity) tileentity).setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, random.nextLong()); ++ // CraftBukkit start - transform block to ensure loot table is accessible ++ /* ++ worldaccess.setBlock(blockposition, (IBlockData) Blocks.CHEST.defaultBlockState().setValue(BlockChest.WATERLOGGED, worldaccess.getFluidState(blockposition).is(TagsFluid.WATER)), 2); ++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition); ++ ++ if (tileentity instanceof TileEntityChest) { ++ ((TileEntityChest) tileentity).setLootTable(this.isLarge ? LootTables.UNDERWATER_RUIN_BIG : LootTables.UNDERWATER_RUIN_SMALL, randomsource.nextLong()); + } ++ */ ++ org.bukkit.craftbukkit.block.CraftChest craftChest = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, world.getFluidState(pos).is(FluidTags.WATER)), null); ++ craftChest.setSeed(random.nextLong()); ++ craftChest.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL)); ++ this.placeCraftBlockEntity(world, pos, craftChest, 2); ++ // CraftBukkit end + } else if ("drowned".equals(metadata)) { + Drowned entitydrowned = (Drowned) EntityType.DROWNED.create(world.getLevel(), EntitySpawnReason.STRUCTURE); + diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch new file mode 100644 index 0000000000..d667e572c3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java +@@ -79,7 +79,12 @@ + ResourceKey resourcekey = (ResourceKey) ShipwreckPieces.MARKERS_TO_LOOT.get(metadata); + + if (resourcekey != null) { +- RandomizableContainer.setBlockEntityLootTable(world, random, pos.below(), resourcekey); ++ // CraftBukkit start - ensure block transformation ++ /* ++ RandomizableContainer.setBlockEntityLootTable(worldaccess, randomsource, blockposition.below(), resourcekey); ++ */ ++ this.setCraftLootTable(world, pos.below(), random, resourcekey); ++ // CraftBukkit end + } + + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch new file mode 100644 index 0000000000..90efca8745 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch @@ -0,0 +1,55 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java +@@ -8,7 +8,6 @@ + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.util.RandomSource; +-import net.minecraft.world.entity.EntityType; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.StructureManager; + import net.minecraft.world.level.WorldGenLevel; +@@ -22,8 +21,6 @@ + import net.minecraft.world.level.block.SlabBlock; + import net.minecraft.world.level.block.StairBlock; + import net.minecraft.world.level.block.WallTorchBlock; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; + import net.minecraft.world.level.block.state.properties.SlabType; +@@ -53,7 +50,7 @@ + public boolean doPlace(int chainLength) { + return super.doPlace(chainLength) && chainLength > 5; + } +- }}; ++ } }; // CraftBukkit - fix decompile styling + private static List currentPieces; + static Class imposedPiece; + private static int totalWeight; +@@ -1136,14 +1133,19 @@ + + if (chunkBox.isInside(blockposition_mutableblockposition)) { + this.hasPlacedSpawner = true; +- world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); +- BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition); +- +- if (tileentity instanceof SpawnerBlockEntity) { +- SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity; +- +- tileentitymobspawner.setEntityId(EntityType.SILVERFISH, random); ++ // CraftBukkit start ++ /* ++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2); ++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition); ++ ++ if (tileentity instanceof TileEntityMobSpawner) { ++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity; ++ ++ tileentitymobspawner.setEntityId(EntityTypes.SILVERFISH, randomsource); + } ++ */ ++ this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.SILVERFISH, 2); ++ // CraftBukkit end + } + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch new file mode 100644 index 0000000000..ad3c0e1641 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java +@@ -100,7 +100,7 @@ + entitywitch.setPersistenceRequired(); + entitywitch.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F); + entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(blockposition_mutableblockposition), EntitySpawnReason.STRUCTURE, (SpawnGroupData) null); +- world.addFreshEntityWithPassengers(entitywitch); ++ world.addFreshEntityWithPassengers(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason + } + } + } +@@ -121,7 +121,7 @@ + entitycat.setPersistenceRequired(); + entitycat.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F); + entitycat.finalizeSpawn(world, world.getCurrentDifficultyAt(blockposition_mutableblockposition), EntitySpawnReason.STRUCTURE, (SpawnGroupData) null); +- world.addFreshEntityWithPassengers(entitycat); ++ world.addFreshEntityWithPassengers(entitycat, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason + } + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch new file mode 100644 index 0000000000..60b3e8e667 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java +@@ -22,7 +22,7 @@ + private LiquidSettings liquidSettings; + @Nullable + private RandomSource random; +- private int palette; ++ public int palette = -1; // CraftBukkit - Set initial value so we know if the palette has been set forcefully + private final List processors; + private boolean knownShape; + private boolean finalizeEntities; +@@ -149,6 +149,13 @@ + + if (i == 0) { + throw new IllegalStateException("No palettes"); ++ // CraftBukkit start ++ } else if (this.palette >= 0) { ++ if (this.palette >= i) { ++ throw new IllegalArgumentException("Palette index out of bounds. Got " + this.palette + " where there are only " + i + " palettes available."); ++ } ++ return infoLists.get(this.palette); ++ // CraftBukkit end + } else { + return (StructureTemplate.Palette) infoLists.get(this.getRandom(pos).nextInt(i)); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch new file mode 100644 index 0000000000..6bbbc67254 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch @@ -0,0 +1,182 @@ +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -25,6 +25,7 @@ + import net.minecraft.nbt.IntTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtUtils; ++import net.minecraft.nbt.Tag; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.util.RandomSource; + import net.minecraft.world.Clearable; +@@ -55,6 +56,9 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; + import net.minecraft.world.phys.shapes.DiscreteVoxelShape; ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; ++// CraftBukkit end + + public class StructureTemplate { + +@@ -74,6 +78,11 @@ + private Vec3i size; + private String author; + ++ // CraftBukkit start - data containers ++ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); ++ public CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(StructureTemplate.DATA_TYPE_REGISTRY); ++ // CraftBukkit end ++ + public StructureTemplate() { + this.size = Vec3i.ZERO; + this.author = "?"; +@@ -147,7 +156,7 @@ + } + + private static List buildInfoList(List fullBlocks, List blocksWithNbt, List otherBlocks) { +- Comparator comparator = Comparator.comparingInt((definedstructure_blockinfo) -> { ++ Comparator comparator = Comparator.comparingInt((definedstructure_blockinfo) -> { // CraftBukkit - decompile error + return definedstructure_blockinfo.pos.getY(); + }).thenComparingInt((definedstructure_blockinfo) -> { + return definedstructure_blockinfo.pos.getX(); +@@ -253,6 +262,19 @@ + if (this.palettes.isEmpty()) { + return false; + } else { ++ // CraftBukkit start ++ // We only want the TransformerGeneratorAccess at certain locations because in here are many "block update" calls that shouldn't be transformed ++ ServerLevelAccessor wrappedAccess = world; ++ org.bukkit.craftbukkit.util.CraftStructureTransformer structureTransformer = null; ++ if (wrappedAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) { ++ world = transformerAccess.getHandle(); ++ structureTransformer = transformerAccess.getStructureTransformer(); ++ // The structureTransformer is not needed if we can not transform blocks therefore we can save a little bit of performance doing this ++ if (structureTransformer != null && !structureTransformer.canTransformBlocks()) { ++ structureTransformer = null; ++ } ++ } ++ // CraftBukkit end + List list = placementData.getRandomPalette(this.palettes, pos).blocks(); + + if ((!list.isEmpty() || !placementData.isIgnoreEntities() && !this.entityInfoList.isEmpty()) && this.size.getX() >= 1 && this.size.getY() >= 1 && this.size.getZ() >= 1) { +@@ -281,9 +303,27 @@ + + if (definedstructure_blockinfo.nbt != null) { + tileentity = world.getBlockEntity(blockposition2); +- Clearable.tryClear(tileentity); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ Clearable.tryClear(tileentity); ++ } ++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock + world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20); + } ++ // CraftBukkit start ++ if (structureTransformer != null) { ++ org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition2, iblockdata, null); ++ if (definedstructure_blockinfo.nbt != null && craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState entityState) { ++ entityState.loadData(definedstructure_blockinfo.nbt); ++ if (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftLootable craftLootable) { ++ craftLootable.setSeed(random.nextLong()); ++ } ++ } ++ craftBlockState = structureTransformer.transformCraftState(craftBlockState); ++ iblockdata = craftBlockState.getHandle(); ++ definedstructure_blockinfo = new StructureTemplate.StructureBlockInfo(blockposition2, iblockdata, (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState craftBlockEntityState ? craftBlockEntityState.getSnapshotNBT() : null)); ++ } ++ // CraftBukkit end + + if (world.setBlock(blockposition2, iblockdata, flags)) { + j = Math.min(j, blockposition2.getX()); +@@ -296,7 +336,7 @@ + if (definedstructure_blockinfo.nbt != null) { + tileentity = world.getBlockEntity(blockposition2); + if (tileentity != null) { +- if (tileentity instanceof RandomizableContainer) { ++ if (structureTransformer == null && tileentity instanceof RandomizableContainer) { // CraftBukkit - only process if don't have a transformer access (Was already set above) - SPIGOT-7520: Use structureTransformer as check, so that it is the same as above + definedstructure_blockinfo.nbt.putLong("LootTableSeed", random.nextLong()); + } + +@@ -394,14 +434,18 @@ + if (pair1.getSecond() != null) { + tileentity = world.getBlockEntity(blockposition6); + if (tileentity != null) { +- tileentity.setChanged(); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ tileentity.setChanged(); ++ } ++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock + } + } + } + } + + if (!placementData.isIgnoreEntities()) { +- this.placeEntities(world, pos, placementData.getMirror(), placementData.getRotation(), placementData.getRotationPivot(), structureboundingbox, placementData.shouldFinalizeEntities()); ++ this.placeEntities(wrappedAccess, pos, placementData.getMirror(), placementData.getRotation(), placementData.getRotationPivot(), structureboundingbox, placementData.shouldFinalizeEntities()); // CraftBukkit + } + + return true; +@@ -503,11 +547,13 @@ + } + + private static Optional createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) { +- try { +- return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE); +- } catch (Exception exception) { +- return Optional.empty(); +- } ++ // CraftBukkit start ++ // try { ++ return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE, true); // Paper - Don't fire sync event during generation ++ // } catch (Exception exception) { ++ // return Optional.empty(); ++ // } ++ // CraftBukkit end + } + + public Vec3i getSize(Rotation rotation) { +@@ -721,6 +767,11 @@ + + nbt.put("entities", nbttaglist3); + nbt.put("size", this.newIntegerList(this.size.getX(), this.size.getY(), this.size.getZ())); ++ // CraftBukkit start - PDC ++ if (!this.persistentDataContainer.isEmpty()) { ++ nbt.put("BukkitValues", this.persistentDataContainer.toTagCompound()); ++ } ++ // CraftBukkit end + return NbtUtils.addCurrentDataVersion(nbt); + } + +@@ -760,6 +811,12 @@ + } + } + ++ // CraftBukkit start - PDC ++ Tag base = nbt.get("BukkitValues"); ++ if (base instanceof CompoundTag) { ++ this.persistentDataContainer.putAll((CompoundTag) base); ++ } ++ // CraftBukkit end + } + + private void loadPalette(HolderGetter blockLookup, ListTag palette, ListTag blocks) { +@@ -840,7 +897,7 @@ + public static final class Palette { + + private final List blocks; +- private final Map> cache = Maps.newHashMap(); ++ private final Map> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads + @Nullable + private List cachedJigsaws; + +@@ -924,7 +981,7 @@ + public BlockState stateFor(int id) { + BlockState iblockdata = (BlockState) this.ids.byId(id); + +- return iblockdata == null ? StructureTemplate.SimplePalette.DEFAULT_BLOCK_STATE : iblockdata; ++ return iblockdata == null ? SimplePalette.DEFAULT_BLOCK_STATE : iblockdata; // CraftBukkit - decompile error + } + + public Iterator iterator() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch new file mode 100644 index 0000000000..cfc052e612 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch @@ -0,0 +1,157 @@ +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java +@@ -31,6 +31,14 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++// CraftBukkit start ++import org.bukkit.block.BlockFace; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.block.BlockFromToEvent; ++import org.bukkit.event.block.FluidLevelChangeEvent; ++// CraftBukkit end + + public abstract class FlowingFluid extends Fluid { + +@@ -135,6 +143,15 @@ + Fluid fluidtype = fluid2.getType(); + + if (fluid1.canBeReplacedWith(world, blockposition1, fluidtype, Direction.DOWN) && FlowingFluid.canHoldSpecificFluid(world, blockposition1, iblockdata1, fluidtype)) { ++ // CraftBukkit start ++ org.bukkit.block.Block source = CraftBlock.at(world, fluidPos); ++ BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + this.spreadTo(world, blockposition1, iblockdata1, Direction.DOWN, fluid2); + if (this.sourceNeighborCount(world, fluidPos) >= 3) { + this.spreadToSides(world, fluidPos, fluidState, blockState); +@@ -167,8 +184,19 @@ + Direction enumdirection = (Direction) entry.getKey(); + FluidState fluid1 = (FluidState) entry.getValue(); + BlockPos blockposition1 = pos.relative(enumdirection); ++ final BlockState blockStateIfLoaded = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing + +- this.spreadTo(world, blockposition1, world.getBlockState(blockposition1), enumdirection, fluid1); ++ // CraftBukkit start ++ org.bukkit.block.Block source = CraftBlock.at(world, pos); ++ BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection)); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ continue; ++ } ++ // CraftBukkit end ++ this.spreadTo(world, blockposition1, blockStateIfLoaded, enumdirection, fluid1); // Paper - Prevent chunk loading from fluid flowing + } + + } +@@ -183,7 +211,8 @@ + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.setWithOffset(pos, enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition1); ++ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition_mutableblockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (fluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(enumdirection, world, pos, state, blockposition_mutableblockposition1, iblockdata1)) { +@@ -287,7 +316,7 @@ + ifluidcontainer.placeLiquid(world, pos, state, fluidState); + } else { + if (!state.isAir()) { +- this.beforeDestroyingBlock(world, pos, state); ++ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent + } + + world.setBlock(pos, fluidState.createLegacyBlock(), 3); +@@ -295,6 +324,7 @@ + + } + ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent + protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state); + + protected int getSlopeDistance(LevelReader world, BlockPos pos, int i, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadCache) { +@@ -306,7 +336,8 @@ + + if (enumdirection1 != direction) { + BlockPos blockposition1 = pos.relative(enumdirection1); +- BlockState iblockdata1 = spreadCache.getBlockState(blockposition1); ++ BlockState iblockdata1 = spreadCache.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (this.canPassThrough(world, this.getFlowing(), pos, state, enumdirection1, blockposition1, iblockdata1, fluid)) { +@@ -372,7 +403,8 @@ + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + BlockPos blockposition1 = pos.relative(enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (this.canMaybePassThrough(world, pos, state, enumdirection, blockposition1, iblockdata1, fluid)) { +@@ -444,10 +476,24 @@ + if (fluid1.isEmpty()) { + fluidState = fluid1; + blockState = Blocks.AIR.defaultBlockState(); ++ // CraftBukkit start ++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(world, pos, blockState); ++ if (event.isCancelled()) { ++ return; ++ } ++ blockState = ((CraftBlockData) event.getNewData()).getState(); ++ // CraftBukkit end + world.setBlock(pos, blockState, 3); + } else if (!fluid1.equals(fluidState)) { + fluidState = fluid1; + blockState = fluid1.createLegacyBlock(); ++ // CraftBukkit start ++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(world, pos, blockState); ++ if (event.isCancelled()) { ++ return; ++ } ++ blockState = ((CraftBlockData) event.getNewData()).getState(); ++ // CraftBukkit end + world.setBlock(pos, blockState, 3); + world.scheduleTick(pos, fluid1.getType(), i); + } +@@ -524,12 +570,27 @@ + public BlockState getBlockState(BlockPos pos) { + return this.getBlockState(pos, this.getCacheKey(pos)); + } ++ // Paper start - Prevent chunk loading from fluid flowing ++ public @javax.annotation.Nullable BlockState getBlockStateIfLoaded(BlockPos pos) { ++ return this.getBlockState(pos, this.getCacheKey(pos), false); ++ } ++ // Paper end - Prevent chunk loading from fluid flowing + + private BlockState getBlockState(BlockPos pos, short packed) { +- return (BlockState) this.stateCache.computeIfAbsent(packed, (short1) -> { +- return this.level.getBlockState(pos); +- }); ++ // Paper start - Prevent chunk loading from fluid flowing ++ return getBlockState(pos, packed, true); + } ++ private @javax.annotation.Nullable BlockState getBlockState(BlockPos pos, short packed, boolean load) { ++ BlockState blockState = this.stateCache.get(packed); ++ if (blockState == null) { ++ blockState = load ? level.getBlockState(pos) : level.getBlockStateIfLoaded(pos); ++ if (blockState != null) { ++ this.stateCache.put(packed, blockState); ++ } ++ } ++ return blockState; ++ // Paper end - Prevent chunk loading from fluid flowing ++ } + + public boolean isHole(BlockPos pos) { + return this.holeCache.computeIfAbsent(this.getCacheKey(pos), (short0) -> { diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch new file mode 100644 index 0000000000..78b4ee0a64 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch @@ -0,0 +1,23 @@ +--- a/net/minecraft/world/level/material/FluidState.java ++++ b/net/minecraft/world/level/material/FluidState.java +@@ -26,9 +26,11 @@ + public static final Codec CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable(); + public static final int AMOUNT_MAX = 9; + public static final int AMOUNT_FULL = 8; ++ protected final boolean isEmpty; // Paper - Perf: moved from isEmpty() + + public FluidState(Fluid fluid, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + super(fluid, propertyMap, codec); ++ this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty() + } + + public Fluid getType() { +@@ -44,7 +46,7 @@ + } + + public boolean isEmpty() { +- return this.getType().isEmpty(); ++ return this.isEmpty; // Paper - Perf: moved into constructor + } + + public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch new file mode 100644 index 0000000000..de343949f4 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -85,6 +85,13 @@ + + if (iblockdata.isAir()) { + if (this.hasFlammableNeighbours(world, blockposition1)) { ++ // CraftBukkit start - Prevent lava putting something on fire ++ if (world.getBlockState(blockposition1).getBlock() != Blocks.FIRE) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition1, pos).isCancelled()) { ++ continue; ++ } ++ } ++ // CraftBukkit end + world.setBlockAndUpdate(blockposition1, BaseFireBlock.getState(world, blockposition1)); + return; + } +@@ -101,6 +108,14 @@ + } + + if (world.isEmptyBlock(blockposition2.above()) && this.isFlammable(world, blockposition2)) { ++ // CraftBukkit start - Prevent lava putting something on fire ++ BlockPos up = blockposition2.above(); ++ if (world.getBlockState(up).getBlock() != Blocks.FIRE) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, up, pos).isCancelled()) { ++ continue; ++ } ++ } ++ // CraftBukkit end + world.setBlockAndUpdate(blockposition2.above(), BaseFireBlock.getState(world, blockposition2)); + } + } +@@ -196,7 +211,11 @@ + + if (this.is(FluidTags.LAVA) && fluid1.is(FluidTags.WATER)) { + if (state.getBlock() instanceof LiquidBlock) { +- world.setBlock(pos, Blocks.STONE.defaultBlockState(), 3); ++ // CraftBukkit start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world.getMinecraftWorld(), pos, Blocks.STONE.defaultBlockState(), 3)) { ++ return; ++ } ++ // CraftBukkit end + } + + this.fizz(world, pos); +@@ -214,7 +233,7 @@ + + @Override + protected float getExplosionResistance() { +- return 100.0F; ++ return Blocks.LAVA.getExplosionResistance(); // Paper - Get explosion resistance from actual block + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch new file mode 100644 index 0000000000..f615848dea --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/material/WaterFluid.java ++++ b/net/minecraft/world/level/material/WaterFluid.java +@@ -81,7 +81,14 @@ + return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); + } + ++ // Paper start - Add BlockBreakBlockEvent + @Override ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { ++ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; ++ Block.dropResources(state, world, pos, tileentity, source); ++ } ++ // Paper end - Add BlockBreakBlockEvent ++ @Override + protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) { + BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; + Block.dropResources(state, world, pos, blockEntity); +@@ -119,7 +126,7 @@ + + @Override + protected float getExplosionResistance() { +- return 100.0F; ++ return Blocks.WATER.getExplosionResistance(); // Paper - Get explosion resistance from actual block + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch new file mode 100644 index 0000000000..94eb8f4856 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/pathfinder/Path.java ++++ b/net/minecraft/world/level/pathfinder/Path.java +@@ -18,6 +18,7 @@ + private final BlockPos target; + private final float distToTarget; + private final boolean reached; ++ public boolean hasNext() { return getNextNodeIndex() < this.nodes.size(); } // Paper - Mob Pathfinding API + + public Path(List nodes, BlockPos target, boolean reachesTarget) { + this.nodes = nodes; diff --git a/paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch new file mode 100644 index 0000000000..96be982191 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -478,7 +478,12 @@ + } + + protected static PathType getPathTypeFromState(BlockGetter world, BlockPos pos) { +- BlockState blockState = world.getBlockState(pos); ++ // Paper start - Do not load chunks during pathfinding ++ BlockState blockState = world.getBlockStateIfLoaded(pos); ++ if (blockState == null) { ++ return PathType.BLOCKED; ++ } ++ // Paper end + Block block = blockState.getBlock(); + if (blockState.isAir()) { + return PathType.OPEN; diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch new file mode 100644 index 0000000000..2bcc6db1ba --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch @@ -0,0 +1,149 @@ +--- a/net/minecraft/world/level/portal/PortalForcer.java ++++ b/net/minecraft/world/level/portal/PortalForcer.java +@@ -42,34 +42,52 @@ + this.level = world; + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper + public Optional findClosestPortalPosition(BlockPos pos, boolean destIsNether, WorldBorder worldBorder) { ++ // CraftBukkit start ++ return this.findClosestPortalPosition(pos, worldBorder, destIsNether ? 16 : 128); // Search Radius ++ } ++ ++ public Optional findClosestPortalPosition(BlockPos blockposition, WorldBorder worldborder, int i) { + PoiManager villageplace = this.level.getPoiManager(); +- int i = destIsNether ? 16 : 128; ++ // int i = flag ? 16 : 128; ++ // CraftBukkit end + +- villageplace.ensureLoadedAndValid(this.level, pos, i); +- Stream stream = villageplace.getInSquare((holder) -> { ++ villageplace.ensureLoadedAndValid(this.level, blockposition, i); ++ Stream stream = villageplace.getInSquare((holder) -> { // CraftBukkit - decompile error + return holder.is(PoiTypes.NETHER_PORTAL); +- }, pos, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos); ++ }, blockposition, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos); + +- Objects.requireNonNull(worldBorder); +- return stream.filter(worldBorder::isWithinBounds).filter((blockposition1) -> { ++ Objects.requireNonNull(worldborder); ++ return stream.filter(worldborder::isWithinBounds).filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))).filter((blockposition1) -> { // Paper - Configurable nether ceiling damage + return this.level.getBlockState(blockposition1).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); +- }).min(Comparator.comparingDouble((blockposition1) -> { +- return blockposition1.distSqr(pos); ++ }).min(Comparator.comparingDouble((BlockPos blockposition1) -> { // CraftBukkit - decompile error ++ return blockposition1.distSqr(blockposition); + }).thenComparingInt(Vec3i::getY)); + } + + public Optional createPortal(BlockPos pos, Direction.Axis axis) { +- Direction enumdirection = Direction.get(Direction.AxisDirection.POSITIVE, axis); ++ // CraftBukkit start ++ return this.createPortal(pos, axis, null, 16); ++ } ++ ++ public Optional createPortal(BlockPos blockposition, Direction.Axis enumdirection_enumaxis, net.minecraft.world.entity.Entity entity, int createRadius) { ++ // CraftBukkit end ++ Direction enumdirection = Direction.get(Direction.AxisDirection.POSITIVE, enumdirection_enumaxis); + double d0 = -1.0D; + BlockPos blockposition1 = null; + double d1 = -1.0D; + BlockPos blockposition2 = null; + WorldBorder worldborder = this.level.getWorldBorder(); + int i = Math.min(this.level.getMaxY(), this.level.getMinY() + this.level.getLogicalHeight() - 1); ++ // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height ++ if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) { ++ i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1); ++ } ++ // Paper end - Configurable nether ceiling damage + boolean flag = true; +- BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); +- Iterator iterator = BlockPos.spiralAround(pos, 16, Direction.EAST, Direction.SOUTH).iterator(); ++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); ++ Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit + + int j; + int k; +@@ -95,7 +113,7 @@ + if (i1 <= 0 || i1 >= 3) { + blockposition_mutableblockposition1.setY(k); + if (this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, 0)) { +- double d2 = pos.distSqr(blockposition_mutableblockposition1); ++ double d2 = blockposition.distSqr(blockposition_mutableblockposition1); + + if (this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, -1) && this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, 1) && (d0 == -1.0D || d0 > d2)) { + d0 = d2; +@@ -122,6 +140,7 @@ + int j1; + int k1; + ++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(this.level); // CraftBukkit - Use BlockStateListPopulator + if (d0 == -1.0D) { + j1 = Math.max(this.level.getMinY() - -1, 70); + k1 = i - 9; +@@ -129,7 +148,7 @@ + return Optional.empty(); + } + +- blockposition1 = (new BlockPos(pos.getX() - enumdirection.getStepX() * 1, Mth.clamp(pos.getY(), j1, k1), pos.getZ() - enumdirection.getStepZ() * 1)).immutable(); ++ blockposition1 = (new BlockPos(blockposition.getX() - enumdirection.getStepX() * 1, Mth.clamp(blockposition.getY(), j1, k1), blockposition.getZ() - enumdirection.getStepZ() * 1)).immutable(); + blockposition1 = worldborder.clampToBounds(blockposition1); + Direction enumdirection1 = enumdirection.getClockWise(); + +@@ -139,7 +158,7 @@ + BlockState iblockdata = i1 < 0 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState(); + + blockposition_mutableblockposition.setWithOffset(blockposition1, l * enumdirection.getStepX() + k * enumdirection1.getStepX(), i1, l * enumdirection.getStepZ() + k * enumdirection1.getStepZ()); +- this.level.setBlockAndUpdate(blockposition_mutableblockposition, iblockdata); ++ blockList.setBlock(blockposition_mutableblockposition, iblockdata, 3); // CraftBukkit + } + } + } +@@ -149,20 +168,30 @@ + for (k1 = -1; k1 < 4; ++k1) { + if (j1 == -1 || j1 == 2 || k1 == -1 || k1 == 3) { + blockposition_mutableblockposition.setWithOffset(blockposition1, j1 * enumdirection.getStepX(), k1, j1 * enumdirection.getStepZ()); +- this.level.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3); ++ blockList.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3); // CraftBukkit + } + } + } + +- BlockState iblockdata1 = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, axis); ++ BlockState iblockdata1 = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, enumdirection_enumaxis); + + for (k1 = 0; k1 < 2; ++k1) { + for (j = 0; j < 3; ++j) { + blockposition_mutableblockposition.setWithOffset(blockposition1, k1 * enumdirection.getStepX(), j, k1 * enumdirection.getStepZ()); +- this.level.setBlock(blockposition_mutableblockposition, iblockdata1, 18); ++ blockList.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit + } + } + ++ // CraftBukkit start ++ org.bukkit.World bworld = this.level.getWorld(); ++ org.bukkit.event.world.PortalCreateEvent event = new org.bukkit.event.world.PortalCreateEvent((java.util.List) (java.util.List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.NETHER_PAIR); ++ ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return Optional.empty(); ++ } ++ blockList.updateList(); ++ // CraftBukkit end + return Optional.of(new BlockUtil.FoundRectangle(blockposition1.immutable(), 2, 3)); + } + +@@ -178,6 +207,13 @@ + for (int j = -1; j < 3; ++j) { + for (int k = -1; k < 4; ++k) { + temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { ++ if (!this.level.getBlockState(temp).isDestroyable()) { ++ return false; ++ } ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + if (k < 0 && !this.level.getBlockState(temp).isSolid()) { + return false; + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch new file mode 100644 index 0000000000..f0cf745dd6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch @@ -0,0 +1,226 @@ +--- a/net/minecraft/world/level/portal/PortalShape.java ++++ b/net/minecraft/world/level/portal/PortalShape.java +@@ -23,6 +23,11 @@ + import net.minecraft.world.phys.shapes.VoxelShape; + import org.apache.commons.lang3.mutable.MutableInt; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.util.BlockStateListPopulator; ++import org.bukkit.event.world.PortalCreateEvent; ++// CraftBukkit end ++ + public class PortalShape { + + private static final int MIN_WIDTH = 2; +@@ -40,14 +45,18 @@ + private final BlockPos bottomLeft; + private final int height; + private final int width; ++ // CraftBukkit start - add field ++ private final BlockStateListPopulator blocks; + +- private PortalShape(Direction.Axis axis, int foundPortalBlocks, Direction negativeDir, BlockPos lowerCorner, int width, int height) { +- this.axis = axis; +- this.numPortalBlocks = foundPortalBlocks; +- this.rightDir = negativeDir; +- this.bottomLeft = lowerCorner; +- this.width = width; +- this.height = height; ++ private PortalShape(Direction.Axis enumdirection_enumaxis, int i, Direction enumdirection, BlockPos blockposition, int j, int k, BlockStateListPopulator blocks) { ++ this.blocks = blocks; ++ // CraftBukkit end ++ this.axis = enumdirection_enumaxis; ++ this.numPortalBlocks = i; ++ this.rightDir = enumdirection; ++ this.bottomLeft = blockposition; ++ this.width = j; ++ this.height = k; + } + + public static Optional findEmptyPortalShape(LevelAccessor world, BlockPos pos, Direction.Axis firstCheckedAxis) { +@@ -69,110 +78,118 @@ + } + + public static PortalShape findAnyShape(BlockGetter world, BlockPos pos, Direction.Axis axis) { ++ BlockStateListPopulator blocks = new BlockStateListPopulator(((LevelAccessor) world).getMinecraftWorld()); // CraftBukkit + Direction enumdirection = axis == Direction.Axis.X ? Direction.WEST : Direction.SOUTH; +- BlockPos blockposition1 = PortalShape.calculateBottomLeft(world, enumdirection, pos); ++ BlockPos blockposition1 = PortalShape.calculateBottomLeft(world, enumdirection, pos, blocks); // CraftBukkit + + if (blockposition1 == null) { +- return new PortalShape(axis, 0, enumdirection, pos, 0, 0); ++ return new PortalShape(axis, 0, enumdirection, pos, 0, 0, blocks); // CraftBukkit + } else { +- int i = PortalShape.calculateWidth(world, blockposition1, enumdirection); ++ int i = PortalShape.calculateWidth(world, blockposition1, enumdirection, blocks); // CraftBukkit + + if (i == 0) { +- return new PortalShape(axis, 0, enumdirection, blockposition1, 0, 0); ++ return new PortalShape(axis, 0, enumdirection, blockposition1, 0, 0, blocks); // CraftBukkit + } else { + MutableInt mutableint = new MutableInt(); +- int j = PortalShape.calculateHeight(world, blockposition1, enumdirection, i, mutableint); ++ int j = PortalShape.calculateHeight(world, blockposition1, enumdirection, i, mutableint, blocks); // CraftBukkit + +- return new PortalShape(axis, mutableint.getValue(), enumdirection, blockposition1, i, j); ++ return new PortalShape(axis, mutableint.getValue(), enumdirection, blockposition1, i, j, blocks); // CraftBukkit + } + } + } + + @Nullable +- private static BlockPos calculateBottomLeft(BlockGetter world, Direction direction, BlockPos pow) { +- for (int i = Math.max(world.getMinY(), pow.getY() - 21); pow.getY() > i && PortalShape.isEmpty(world.getBlockState(pow.below())); pow = pow.below()) { ++ private static BlockPos calculateBottomLeft(BlockGetter iblockaccess, Direction enumdirection, BlockPos blockposition, BlockStateListPopulator blocks) { // CraftBukkit ++ for (int i = Math.max(iblockaccess.getMinY(), blockposition.getY() - 21); blockposition.getY() > i && PortalShape.isEmpty(iblockaccess.getBlockState(blockposition.below())); blockposition = blockposition.below()) { + ; + } + +- Direction enumdirection1 = direction.getOpposite(); +- int j = PortalShape.getDistanceUntilEdgeAboveFrame(world, pow, enumdirection1) - 1; ++ Direction enumdirection1 = enumdirection.getOpposite(); ++ int j = PortalShape.getDistanceUntilEdgeAboveFrame(iblockaccess, blockposition, enumdirection1, blocks) - 1; // CraftBukkit + +- return j < 0 ? null : pow.relative(enumdirection1, j); ++ return j < 0 ? null : blockposition.relative(enumdirection1, j); + } + +- private static int calculateWidth(BlockGetter world, BlockPos lowerCorner, Direction negativeDir) { +- int i = PortalShape.getDistanceUntilEdgeAboveFrame(world, lowerCorner, negativeDir); ++ private static int calculateWidth(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockStateListPopulator blocks) { // CraftBukkit ++ int i = PortalShape.getDistanceUntilEdgeAboveFrame(iblockaccess, blockposition, enumdirection, blocks); // CraftBukkit + + return i >= 2 && i <= 21 ? i : 0; + } + +- private static int getDistanceUntilEdgeAboveFrame(BlockGetter world, BlockPos lowerCorner, Direction negativeDir) { ++ private static int getDistanceUntilEdgeAboveFrame(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockStateListPopulator blocks) { // CraftBukkit + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + + for (int i = 0; i <= 21; ++i) { +- blockposition_mutableblockposition.set(lowerCorner).move(negativeDir, i); +- BlockState iblockdata = world.getBlockState(blockposition_mutableblockposition); ++ blockposition_mutableblockposition.set(blockposition).move(enumdirection, i); ++ BlockState iblockdata = iblockaccess.getBlockState(blockposition_mutableblockposition); + + if (!PortalShape.isEmpty(iblockdata)) { +- if (PortalShape.FRAME.test(iblockdata, world, blockposition_mutableblockposition)) { ++ if (PortalShape.FRAME.test(iblockdata, iblockaccess, blockposition_mutableblockposition)) { ++ blocks.setBlock(blockposition_mutableblockposition, iblockdata, 18); // CraftBukkit - lower left / right + return i; + } + break; + } + +- BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition.move(Direction.DOWN)); ++ BlockState iblockdata1 = iblockaccess.getBlockState(blockposition_mutableblockposition.move(Direction.DOWN)); + +- if (!PortalShape.FRAME.test(iblockdata1, world, blockposition_mutableblockposition)) { ++ if (!PortalShape.FRAME.test(iblockdata1, iblockaccess, blockposition_mutableblockposition)) { + break; + } ++ blocks.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit - bottom row + } + + return 0; + } + +- private static int calculateHeight(BlockGetter world, BlockPos lowerCorner, Direction negativeDir, int width, MutableInt foundPortalBlocks) { +- BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); +- int j = PortalShape.getDistanceUntilTop(world, lowerCorner, negativeDir, blockposition_mutableblockposition, width, foundPortalBlocks); ++ private static int calculateHeight(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, int i, MutableInt mutableint, BlockStateListPopulator blocks) { // CraftBukkit ++ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); ++ int j = PortalShape.getDistanceUntilTop(iblockaccess, blockposition, enumdirection, blockposition_mutableblockposition, i, mutableint, blocks); // CraftBukkit + +- return j >= 3 && j <= 21 && PortalShape.hasTopFrame(world, lowerCorner, negativeDir, blockposition_mutableblockposition, width, j) ? j : 0; ++ return j >= 3 && j <= 21 && PortalShape.hasTopFrame(iblockaccess, blockposition, enumdirection, blockposition_mutableblockposition, i, j, blocks) ? j : 0; // CraftBukkit + } + +- private static boolean hasTopFrame(BlockGetter world, BlockPos lowerCorner, Direction direction, BlockPos.MutableBlockPos pos, int width, int height) { +- for (int k = 0; k < width; ++k) { +- BlockPos.MutableBlockPos blockposition_mutableblockposition1 = pos.set(lowerCorner).move(Direction.UP, height).move(direction, k); ++ private static boolean hasTopFrame(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockPos.MutableBlockPos blockposition_mutableblockposition, int i, int j, BlockStateListPopulator blocks) { // CraftBukkit ++ for (int k = 0; k < i; ++k) { ++ BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, k); + +- if (!PortalShape.FRAME.test(world.getBlockState(blockposition_mutableblockposition1), world, blockposition_mutableblockposition1)) { ++ if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition1), iblockaccess, blockposition_mutableblockposition1)) { + return false; + } ++ blocks.setBlock(blockposition_mutableblockposition1, iblockaccess.getBlockState(blockposition_mutableblockposition1), 18); // CraftBukkit - upper row + } + + return true; + } + +- private static int getDistanceUntilTop(BlockGetter world, BlockPos lowerCorner, Direction negativeDir, BlockPos.MutableBlockPos pos, int width, MutableInt foundPortalBlocks) { ++ private static int getDistanceUntilTop(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockPos.MutableBlockPos blockposition_mutableblockposition, int i, MutableInt mutableint, BlockStateListPopulator blocks) { // CraftBukkit + for (int j = 0; j < 21; ++j) { +- pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, -1); +- if (!PortalShape.FRAME.test(world.getBlockState(pos), world, pos)) { ++ blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, -1); ++ if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition), iblockaccess, blockposition_mutableblockposition)) { + return j; + } + +- pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, width); +- if (!PortalShape.FRAME.test(world.getBlockState(pos), world, pos)) { ++ blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, i); ++ if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition), iblockaccess, blockposition_mutableblockposition)) { + return j; + } + +- for (int k = 0; k < width; ++k) { +- pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, k); +- BlockState iblockdata = world.getBlockState(pos); ++ for (int k = 0; k < i; ++k) { ++ blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, k); ++ BlockState iblockdata = iblockaccess.getBlockState(blockposition_mutableblockposition); + + if (!PortalShape.isEmpty(iblockdata)) { + return j; + } + + if (iblockdata.is(Blocks.NETHER_PORTAL)) { +- foundPortalBlocks.increment(); ++ mutableint.increment(); + } + } ++ // CraftBukkit start - left and right ++ blocks.setBlock(blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, -1), iblockaccess.getBlockState(blockposition_mutableblockposition), 18); ++ blocks.setBlock(blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, i), iblockaccess.getBlockState(blockposition_mutableblockposition), 18); ++ // CraftBukkit end + } + + return 21; +@@ -186,12 +203,28 @@ + return this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21; + } + +- public void createPortalBlocks(LevelAccessor world) { ++ // CraftBukkit start - return boolean, add entity ++ public boolean createPortalBlocks(LevelAccessor generatoraccess, Entity entity) { ++ org.bukkit.World bworld = generatoraccess.getMinecraftWorld().getWorld(); ++ ++ // Copy below for loop + BlockState iblockdata = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis); + + BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> { +- world.setBlock(blockposition, iblockdata, 18); ++ this.blocks.setBlock(blockposition, iblockdata, 18); + }); ++ ++ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) this.blocks.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); ++ generatoraccess.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end ++ BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> { ++ generatoraccess.setBlock(blockposition, iblockdata, 18); ++ }); ++ return true; // CraftBukkit + } + + public boolean isComplete() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch new file mode 100644 index 0000000000..1d72cf288c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch @@ -0,0 +1,71 @@ +--- a/net/minecraft/world/level/portal/TeleportTransition.java ++++ b/net/minecraft/world/level/portal/TeleportTransition.java +@@ -8,26 +8,55 @@ + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.Relative; + import net.minecraft.world.phys.Vec3; ++// CraftBukkit start ++import org.bukkit.event.player.PlayerTeleportEvent; + +-public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set relatives, TeleportTransition.PostTeleportTransition postTeleportTransition) { ++public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set relatives, TeleportTransition.PostTeleportTransition postTeleportTransition, PlayerTeleportEvent.TeleportCause cause) { + ++ public TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set relatives, TeleportTransition.PostTeleportTransition postTeleportTransition) { ++ this(newLevel, position, deltaMovement, yRot, xRot, missingRespawnBlock, asPassenger, relatives, postTeleportTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public TeleportTransition(PlayerTeleportEvent.TeleportCause cause) { ++ this(null, Vec3.ZERO, Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), DO_NOTHING, cause); ++ } ++ // CraftBukkit end ++ + public static final TeleportTransition.PostTeleportTransition DO_NOTHING = (entity) -> { + }; + public static final TeleportTransition.PostTeleportTransition PLAY_PORTAL_SOUND = TeleportTransition::playPortalSound; + public static final TeleportTransition.PostTeleportTransition PLACE_PORTAL_TICKET = TeleportTransition::placePortalTicket; + + public TeleportTransition(ServerLevel world, Vec3 pos, Vec3 velocity, float yaw, float pitch, TeleportTransition.PostTeleportTransition postDimensionTransition) { +- this(world, pos, velocity, yaw, pitch, Set.of(), postDimensionTransition); ++ // CraftBukkit start ++ this(world, pos, velocity, yaw, pitch, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + ++ public TeleportTransition(ServerLevel worldserver, Vec3 vec3d, Vec3 vec3d1, float f, float f1, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) { ++ this(worldserver, vec3d, vec3d1, f, f1, Set.of(), teleporttransition_a, cause); ++ // CraftBukkit end ++ } ++ + public TeleportTransition(ServerLevel world, Vec3 pos, Vec3 velocity, float yaw, float pitch, Set flags, TeleportTransition.PostTeleportTransition postDimensionTransition) { +- this(world, pos, velocity, yaw, pitch, false, false, flags, postDimensionTransition); ++ // CraftBukkit start ++ this(world, pos, velocity, yaw, pitch, flags, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + ++ public TeleportTransition(ServerLevel worldserver, Vec3 vec3d, Vec3 vec3d1, float f, float f1, Set set, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) { ++ this(worldserver, vec3d, vec3d1, f, f1, false, false, set, teleporttransition_a, cause); ++ // CraftBukkit end ++ } ++ + public TeleportTransition(ServerLevel world, Entity entity, TeleportTransition.PostTeleportTransition postDimensionTransition) { +- this(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), postDimensionTransition); ++ // CraftBukkit start ++ this(world, entity, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + ++ public TeleportTransition(ServerLevel worldserver, Entity entity, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) { ++ this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, worldserver.getSharedSpawnAngle(), 0.0F, false, false, Set.of(), teleporttransition_a, cause); // Paper - MC-200092 - fix first spawn pos yaw being ignored ++ // CraftBukkit end ++ } ++ + private static void playPortalSound(Entity entity) { + if (entity instanceof ServerPlayer entityplayer) { + entityplayer.connection.send(new ClientboundLevelEventPacket(1032, BlockPos.ZERO, 0, false)); +@@ -40,7 +69,7 @@ + } + + public static TeleportTransition missingRespawnBlock(ServerLevel world, Entity entity, TeleportTransition.PostTeleportTransition postDimensionTransition) { +- return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, true, false, Set.of(), postDimensionTransition); ++ return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, world.getSharedSpawnAngle(), 0.0F, true, false, Set.of(), postDimensionTransition); // Paper - MC-200092 - fix spawn pos yaw being ignored + } + + private static Vec3 findAdjustedSharedSpawnPos(ServerLevel world, Entity entity) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch new file mode 100644 index 0000000000..d055cf38c0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -135,7 +135,7 @@ + orientation = this.orientation.withFront(direction); + } + +- NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false); ++ NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false, this.sourcePos); // Paper - Add source block to BlockPhysicsEvent + if (this.idx < NeighborUpdater.UPDATE_ORDER.length && NeighborUpdater.UPDATE_ORDER[this.idx] == this.skipDirection) { + this.idx++; + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch new file mode 100644 index 0000000000..8862572ac8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java ++++ b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java +@@ -9,6 +9,10 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.RedStoneWireBlock; + import net.minecraft.world.level.block.state.BlockState; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++// CraftBukkit end + + public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator { + +@@ -20,7 +24,16 @@ + public void updatePowerStrength(Level world, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) { + int i = this.calculateTargetStrength(world, pos); + +- if ((Integer) state.getValue(RedStoneWireBlock.POWER) != i) { ++ // CraftBukkit start ++ int oldPower = state.getValue(RedStoneWireBlock.POWER); ++ if (oldPower != i) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(world, pos), oldPower, i); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ i = event.getNewCurrent(); ++ } ++ if (oldPower != i) { ++ // CraftBukkit end + if (world.getBlockState(pos) == state) { + world.setBlock(pos, (BlockState) state.setValue(RedStoneWireBlock.POWER, i), 2); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch new file mode 100644 index 0000000000..73e662622f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java ++++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java +@@ -16,6 +16,10 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.EnumProperty; + import net.minecraft.world.level.block.state.properties.RedstoneSide; ++// CraftBukkit start ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++// CraftBukkit end + + public class ExperimentalRedstoneWireEvaluator extends RedstoneWireEvaluator { + +@@ -41,7 +45,16 @@ + int j = ExperimentalRedstoneWireEvaluator.unpackPower(i); + BlockState iblockdata1 = world.getBlockState(blockposition1); + +- if (iblockdata1.is((Block) this.wireBlock) && !((Integer) iblockdata1.getValue(RedStoneWireBlock.POWER)).equals(j)) { ++ // CraftBukkit start ++ int oldPower = iblockdata1.getValue(RedStoneWireBlock.POWER); // Paper - Call BlockRedstoneEvent properly; get the previous power from the right state ++ if (oldPower != j) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(world, blockposition1), oldPower, j); ++ world.getCraftServer().getPluginManager().callEvent(event); ++ ++ j = event.getNewCurrent(); ++ } ++ if (iblockdata1.is((Block) this.wireBlock) && oldPower != j) { ++ // CraftBukkit end + int k = 2; + + if (!blockAdded || !flag1) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch new file mode 100644 index 0000000000..a8973333ac --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch @@ -0,0 +1,50 @@ +--- a/net/minecraft/world/level/redstone/NeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/NeighborUpdater.java +@@ -8,11 +8,17 @@ + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.event.block.BlockPhysicsEvent; ++// CraftBukkit end + + public interface NeighborUpdater { + +@@ -49,8 +55,29 @@ + } + + static void executeUpdate(Level world, BlockState state, BlockPos pos, Block sourceBlock, @Nullable Orientation orientation, boolean notify) { ++ // Paper start - Add source block to BlockPhysicsEvent ++ executeUpdate(world, state, pos, sourceBlock, orientation, notify, pos); ++ } ++ ++ static void executeUpdate(Level world, BlockState state, BlockPos pos, Block sourceBlock, @Nullable Orientation orientation, boolean notify, BlockPos sourcePos) { ++ // Paper end - Add source block to BlockPhysicsEvent + try { ++ // CraftBukkit start ++ CraftWorld cworld = ((ServerLevel) world).getWorld(); ++ if (cworld != null) { ++ BlockPhysicsEvent event = new BlockPhysicsEvent(CraftBlock.at(world, pos), CraftBlockData.fromData(state), CraftBlock.at(world, sourcePos)); // Paper - Add source block to BlockPhysicsEvent ++ ((ServerLevel) world).getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + state.handleNeighborChanged(world, pos, sourceBlock, orientation, notify); ++ // Spigot Start ++ } catch (StackOverflowError ex) { ++ world.lastPhysicsProblem = new BlockPos(pos); ++ // Spigot End + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception while updating neighbours"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being updated"); diff --git a/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch new file mode 100644 index 0000000000..452050da4c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch @@ -0,0 +1,216 @@ +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -47,6 +47,17 @@ + import net.minecraft.world.level.saveddata.SavedData; + import org.slf4j.Logger; + ++// CraftBukkit start ++import io.papermc.paper.adventure.PaperAdventure; // Paper ++import java.util.UUID; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.map.CraftMapCursor; ++import org.bukkit.craftbukkit.map.CraftMapView; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++// CraftBukkit end ++ + public class MapItemSavedData extends SavedData { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -70,6 +81,13 @@ + private final Map frameMarkers = Maps.newHashMap(); + private int trackedDecorationCount; + ++ // CraftBukkit start ++ public final CraftMapView mapView; ++ private CraftServer server; ++ public UUID uniqueId = null; ++ public MapId id; ++ // CraftBukkit end ++ + public static SavedData.Factory factory() { + return new SavedData.Factory<>(() -> { + throw new IllegalStateException("Should never create an empty map saved data"); +@@ -84,6 +102,10 @@ + this.trackingPosition = showDecorations; + this.unlimitedTracking = unlimitedTracking; + this.locked = locked; ++ // CraftBukkit start ++ this.mapView = new CraftMapView(this); ++ this.server = (CraftServer) org.bukkit.Bukkit.getServer(); ++ // CraftBukkit end + } + + public static MapItemSavedData createFresh(double centerX, double centerZ, byte scale, boolean showDecorations, boolean unlimitedTracking, ResourceKey dimension) { +@@ -101,12 +123,49 @@ + } + + public static MapItemSavedData load(CompoundTag nbt, HolderLookup.Provider registries) { +- DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); ++ // Paper start - fix "Not a string" spam ++ Tag dimension = nbt.get("dimension"); ++ if (dimension instanceof final net.minecraft.nbt.NumericTag numericTag && numericTag.getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { ++ long least = nbt.getLong("UUIDLeast"); ++ long most = nbt.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ UUID uuid = new UUID(most, least); ++ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid); ++ if (world != null) { ++ dimension = net.minecraft.nbt.StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); ++ } else { ++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } else { ++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } ++ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error ++ // Paper end - fix "Not a string" spam + Logger logger = MapItemSavedData.LOGGER; + + Objects.requireNonNull(logger); +- ResourceKey resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElseThrow(() -> { +- return new IllegalArgumentException("Invalid map dimension: " + String.valueOf(nbt.get("dimension"))); ++ // CraftBukkit start ++ ResourceKey resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElseGet(() -> { ++ long least = nbt.getLong("UUIDLeast"); ++ long most = nbt.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ UUID uniqueId = new UUID(most, least); ++ ++ CraftWorld world = (CraftWorld) Bukkit.getWorld(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. */ ++ // PAIL: Use Vanilla exception handling for now ++ } else { ++ return world.getHandle().dimension(); ++ } ++ } ++ throw new IllegalArgumentException("Invalid map dimension: " + String.valueOf(nbt.get("dimension"))); ++ // CraftBukkit end + }); + int i = nbt.getInt("xCenter"); + int j = nbt.getInt("zCenter"); +@@ -131,7 +190,8 @@ + MapBanner mapiconbanner = (MapBanner) iterator.next(); + + worldmap.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner); +- worldmap.addDecoration(mapiconbanner.getDecoration(), (LevelAccessor) null, mapiconbanner.getId(), (double) mapiconbanner.pos().getX(), (double) mapiconbanner.pos().getZ(), 180.0D, (Component) mapiconbanner.name().orElse((Object) null)); ++ // CraftBukkit - decompile error ++ worldmap.addDecoration(mapiconbanner.getDecoration(), (LevelAccessor) null, mapiconbanner.getId(), (double) mapiconbanner.pos().getX(), (double) mapiconbanner.pos().getZ(), 180.0D, (Component) mapiconbanner.name().orElse(null)); + } + + ListTag nbttaglist = nbt.getList("frames", 10); +@@ -150,13 +210,32 @@ + + @Override + public CompoundTag save(CompoundTag nbt, HolderLookup.Provider registries) { +- DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.dimension.location()); ++ DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.dimension.location()); // CraftBukkit - decompile error + Logger logger = MapItemSavedData.LOGGER; + + Objects.requireNonNull(logger); + dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { + nbt.put("dimension", nbtbase); + }); ++ // CraftBukkit start ++ if (true) { ++ if (this.uniqueId == null) { ++ for (org.bukkit.World world : this.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) { ++ nbt.putLong("UUIDLeast", this.uniqueId.getLeastSignificantBits()); ++ nbt.putLong("UUIDMost", this.uniqueId.getMostSignificantBits()); ++ } ++ } ++ // CraftBukkit end + nbt.putInt("xCenter", this.centerX); + nbt.putInt("zCenter", this.centerZ); + nbt.putByte("scale", this.scale); +@@ -247,8 +326,10 @@ + + MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); + ++ if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps + this.addDecoration(MapDecorationTypes.FRAME, player.level(), MapItemSavedData.getFrameKey(entityitemframe.getId()), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); + this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); ++ } // Paper - Limit item frame cursors on maps + } + + MapDecorations mapdecorations = (MapDecorations) stack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY); +@@ -441,9 +522,9 @@ + return true; + } + +- if (!this.isTrackedCountOverLimit(256)) { ++ if (!this.isTrackedCountOverLimit(((Level) world).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps + this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner); +- this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse((Object) null)); ++ this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse(null)); // CraftBukkit - decompile error + return true; + } + } +@@ -554,7 +635,7 @@ + this.player = entityhuman; + } + +- private MapItemSavedData.MapPatch createPatch() { ++ private MapItemSavedData.MapPatch createPatch(byte[] buffer) { // CraftBukkit + int i = this.minDirtyX; + int j = this.minDirtyY; + int k = this.maxDirtyX + 1 - this.minDirtyX; +@@ -563,7 +644,7 @@ + + for (int i1 = 0; i1 < k; ++i1) { + for (int j1 = 0; j1 < l; ++j1) { +- abyte[i1 + j1 * k] = MapItemSavedData.this.colors[i + i1 + (j + j1) * 128]; ++ abyte[i1 + j1 * k] = buffer[i + i1 + (j + j1) * 128]; // CraftBukkit + } + } + +@@ -573,19 +654,29 @@ + @Nullable + Packet nextUpdatePacket(MapId mapId) { + MapItemSavedData.MapPatch worldmap_c; ++ org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit + + if (this.dirtyData) { + this.dirtyData = false; +- worldmap_c = this.createPatch(); ++ worldmap_c = this.createPatch(render.buffer); // CraftBukkit + } else { + worldmap_c = null; + } + + Collection collection; + +- if (this.dirtyDecorations && this.tick++ % 5 == 0) { ++ if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet + this.dirtyDecorations = false; +- collection = MapItemSavedData.this.decorations.values(); ++ // CraftBukkit start ++ java.util.Collection icons = new java.util.ArrayList(); ++ ++ for (org.bukkit.map.MapCursor cursor : render.cursors) { ++ if (cursor.isVisible()) { ++ icons.add(new MapDecoration(CraftMapCursor.CraftType.bukkitToMinecraftHolder(cursor.getType()), cursor.getX(), cursor.getY(), cursor.getDirection(), Optional.ofNullable(PaperAdventure.asVanilla(cursor.caption())))); ++ } ++ } ++ collection = icons; ++ // CraftBukkit end + } else { + collection = null; + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch new file mode 100644 index 0000000000..0a22aaf58b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -139,7 +139,7 @@ + } else { + int i = Util.maxAllowedExecutorThreads(); + int j = map.size(); +- if (j > i) { ++ if (false && j > i) { // Paper - Separate dimension data IO pool; just throw them into the fixed pool queue + this.pendingWriteFuture = this.pendingWriteFuture.thenCompose(object -> { + List> list = new ArrayList<>(i); + int k = Mth.positiveCeilDiv(j, i); +@@ -160,7 +160,7 @@ + v -> CompletableFuture.allOf( + map.entrySet() + .stream() +- .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.ioPool())) ++ .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.DIMENSION_DATA_IO_POOL)) // Paper - Separate dimension data IO pool + .toArray(CompletableFuture[]::new) + ) + ); diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch new file mode 100644 index 0000000000..e0a1e52b18 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch @@ -0,0 +1,107 @@ +--- a/net/minecraft/world/level/storage/LevelStorageSource.java ++++ b/net/minecraft/world/level/storage/LevelStorageSource.java +@@ -68,7 +68,6 @@ + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelSettings; + import net.minecraft.world.level.WorldDataConfiguration; +-import net.minecraft.world.level.dimension.DimensionType; + import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.levelgen.WorldDimensions; + import net.minecraft.world.level.levelgen.WorldGenSettings; +@@ -149,7 +148,7 @@ + } + + public static WorldDataConfiguration readDataConfig(Dynamic dynamic) { +- DataResult dataresult = WorldDataConfiguration.CODEC.parse(dynamic); ++ DataResult dataresult = WorldDataConfiguration.CODEC.parse(dynamic); // CraftBukkit - decompile error + Logger logger = LevelStorageSource.LOGGER; + + Objects.requireNonNull(logger); +@@ -168,6 +167,7 @@ + WorldDimensions.Complete worlddimensions_b = generatorsettings.dimensions().bake(dimensionsRegistry); + Lifecycle lifecycle = worlddimensions_b.lifecycle().add(registries.allRegistriesLifecycle()); + PrimaryLevelData worlddataserver = PrimaryLevelData.parse(dynamic1, worldsettings, worlddimensions_b.specialWorldProperty(), generatorsettings.options(), lifecycle); ++ worlddataserver.pdc = ((Dynamic) dynamic1).getElement("BukkitValues", null); // CraftBukkit - Add PDC to world + + return new LevelDataAndDimensions(worlddataserver, worlddimensions_b); + } +@@ -409,26 +409,40 @@ + return this.backupDir; + } + +- public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String directoryName) throws IOException, ContentValidationException { +- Path path = this.getLevelPath(directoryName); +- List list = this.worldDirValidator.validateDirectory(path, true); ++ public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String s, ResourceKey dimensionType) throws IOException, ContentValidationException { // CraftBukkit ++ Path path = this.getLevelPath(s); ++ List list = Boolean.getBoolean("paper.disableWorldSymlinkValidation") ? List.of() : this.worldDirValidator.validateDirectory(path, true); // Paper - add skipping of symlinks scan + + if (!list.isEmpty()) { + throw new ContentValidationException(path, list); + } else { +- return new LevelStorageSource.LevelStorageAccess(directoryName, path); ++ return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit + } + } + +- public LevelStorageSource.LevelStorageAccess createAccess(String directoryName) throws IOException { +- Path path = this.getLevelPath(directoryName); ++ public LevelStorageSource.LevelStorageAccess createAccess(String s, ResourceKey dimensionType) throws IOException { // CraftBukkit ++ Path path = this.getLevelPath(s); + +- return new LevelStorageSource.LevelStorageAccess(directoryName, path); ++ return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit + } + + public DirectoryValidator getWorldDirValidator() { + return this.worldDirValidator; ++ } ++ ++ // CraftBukkit start ++ public static Path getStorageFolder(Path path, ResourceKey dimensionType) { ++ if (dimensionType == LevelStem.OVERWORLD) { ++ return path; ++ } else if (dimensionType == LevelStem.NETHER) { ++ return path.resolve("DIM-1"); ++ } else if (dimensionType == LevelStem.END) { ++ return path.resolve("DIM1"); ++ } else { ++ return path.resolve("dimensions").resolve(dimensionType.location().getNamespace()).resolve(dimensionType.location().getPath()); ++ } + } ++ // CraftBukkit end + + public static record LevelCandidates(List levels) implements Iterable { + +@@ -488,8 +502,12 @@ + public final LevelStorageSource.LevelDirectory levelDirectory; + private final String levelId; + private final Map resources = Maps.newHashMap(); ++ // CraftBukkit start ++ public final ResourceKey dimensionType; + +- LevelStorageAccess(final String s, final Path path) throws IOException { ++ LevelStorageAccess(final String s, final Path path, final ResourceKey dimensionType) throws IOException { ++ this.dimensionType = dimensionType; ++ // CraftBukkit end + this.levelId = s; + this.levelDirectory = new LevelStorageSource.LevelDirectory(path); + this.lock = DirectoryLock.create(path); +@@ -529,7 +547,7 @@ + } + + public Path getLevelPath(LevelResource savePath) { +- Map map = this.resources; ++ Map map = this.resources; // CraftBukkit - decompile error + LevelStorageSource.LevelDirectory convertable_b = this.levelDirectory; + + Objects.requireNonNull(this.levelDirectory); +@@ -537,7 +555,7 @@ + } + + public Path getDimensionPath(ResourceKey key) { +- return DimensionType.getStorageFolder(key, this.levelDirectory.path()); ++ return LevelStorageSource.getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit + } + + private void checkLock() { diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch new file mode 100644 index 0000000000..6ed0222e60 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch @@ -0,0 +1,143 @@ +--- a/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -15,8 +15,10 @@ + import net.minecraft.nbt.NbtAccounter; + import net.minecraft.nbt.NbtIo; + import net.minecraft.nbt.NbtUtils; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.entity.player.Player; ++import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.slf4j.Logger; + + public class PlayerDataStorage { +@@ -33,6 +35,7 @@ + } + + public void save(Player player) { ++ if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot + try { + CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag()); + Path path = this.playerDir.toPath(); +@@ -44,39 +47,60 @@ + + Util.safeReplaceFile(path2, path1, path3); + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString()); ++ PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception + } + + } + +- private void backup(Player player, String extension) { ++ private void backup(String name, String s1, String s) { // name, uuid, extension + Path path = this.playerDir.toPath(); +- String s1 = player.getStringUUID(); +- Path path1 = path.resolve(s1 + extension); ++ // String s1 = entityhuman.getStringUUID(); // CraftBukkit - used above ++ Path path1 = path.resolve(s1 + s); + +- s1 = player.getStringUUID(); +- Path path2 = path.resolve(s1 + "_corrupted_" + LocalDateTime.now().format(PlayerDataStorage.FORMATTER) + extension); ++ // s1 = entityhuman.getStringUUID(); // CraftBukkit - used above ++ Path path2 = path.resolve(s1 + "_corrupted_" + LocalDateTime.now().format(PlayerDataStorage.FORMATTER) + s); + + if (Files.isRegularFile(path1, new LinkOption[0])) { + try { + Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to copy the player.dat file for {}", player.getName().getString(), exception); ++ PlayerDataStorage.LOGGER.warn("Failed to copy the player.dat file for {}", name, exception); // CraftBukkit + } + + } + } + +- private Optional load(Player player, String extension) { ++ // CraftBukkit start ++ private Optional load(String name, String s1, String s) { // name, uuid, extension ++ // CraftBukkit end + File file = this.playerDir; +- String s1 = player.getStringUUID(); +- File file1 = new File(file, s1 + extension); ++ // String s1 = entityhuman.getStringUUID(); // CraftBukkit - used above ++ File file1 = new File(file, s1 + s); ++ // Spigot Start ++ boolean usingWrongFile = false; ++ if ( org.bukkit.Bukkit.getOnlineMode() && !file1.exists() ) // Paper - Check online mode first ++ { ++ file1 = new File( file, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + name ).getBytes( java.nio.charset.StandardCharsets.UTF_8 ) ).toString() + s ); ++ if ( file1.exists() ) ++ { ++ usingWrongFile = true; ++ org.bukkit.Bukkit.getServer().getLogger().warning( "Using offline mode UUID file for player " + name + " as it is the only copy we can find." ); ++ } ++ } ++ // Spigot End + + if (file1.exists() && file1.isFile()) { + try { +- return Optional.of(NbtIo.readCompressed(file1.toPath(), NbtAccounter.unlimitedHeap())); ++ // Spigot Start ++ Optional optional = Optional.of(NbtIo.readCompressed(file1.toPath(), NbtAccounter.unlimitedHeap())); ++ if ( usingWrongFile ) ++ { ++ file1.renameTo( new File( file1.getPath() + ".offline-read" ) ); ++ } ++ return optional; ++ // Spigot End + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to load player data for {}", player.getName().getString()); ++ PlayerDataStorage.LOGGER.warn("Failed to load player data for {}", name); // CraftBukkit + } + } + +@@ -84,20 +108,44 @@ + } + + public Optional load(Player player) { +- Optional optional = this.load(player, ".dat"); ++ // CraftBukkit start ++ return this.load(player.getName().getString(), player.getStringUUID()).map((nbttagcompound) -> { ++ if (player instanceof ServerPlayer) { ++ CraftPlayer player1 = (CraftPlayer) player.getBukkitEntity(); ++ // Only update first played if it is older than the one we have ++ long modified = new File(this.playerDir, player.getStringUUID() + ".dat").lastModified(); ++ if (modified < player1.getFirstPlayed()) { ++ player1.setFirstPlayed(modified); ++ } ++ } + ++ player.load(nbttagcompound); // From below ++ return nbttagcompound; ++ }); ++ } ++ ++ public Optional load(String name, String uuid) { ++ // CraftBukkit end ++ Optional optional = this.load(name, uuid, ".dat"); // CraftBukkit ++ + if (optional.isEmpty()) { +- this.backup(player, ".dat"); ++ this.backup(name, uuid, ".dat"); // CraftBukkit + } + + return optional.or(() -> { +- return this.load(player, ".dat_old"); ++ return this.load(name, uuid, ".dat_old"); // CraftBukkit + }).map((nbttagcompound) -> { + int i = NbtUtils.getDataVersion(nbttagcompound, -1); + + nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i); +- player.load(nbttagcompound); ++ // entityhuman.load(nbttagcompound); // CraftBukkit - handled above + return nbttagcompound; + }); + } ++ ++ // CraftBukkit start ++ public File getPlayerDir() { ++ return this.playerDir; ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch new file mode 100644 index 0000000000..131a7c275b --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch @@ -0,0 +1,203 @@ +--- a/net/minecraft/world/level/storage/PrimaryLevelData.java ++++ b/net/minecraft/world/level/storage/PrimaryLevelData.java +@@ -20,15 +20,12 @@ + import net.minecraft.SharedConstants; + import net.minecraft.Util; + import net.minecraft.core.BlockPos; ++import net.minecraft.core.Registry; + import net.minecraft.core.RegistryAccess; + import net.minecraft.core.UUIDUtil; +-import net.minecraft.nbt.CompoundTag; +-import net.minecraft.nbt.ListTag; +-import net.minecraft.nbt.NbtOps; +-import net.minecraft.nbt.NbtUtils; +-import net.minecraft.nbt.StringTag; +-import net.minecraft.nbt.Tag; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.Difficulty; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.GameType; +@@ -36,12 +33,26 @@ + import net.minecraft.world.level.LevelSettings; + import net.minecraft.world.level.WorldDataConfiguration; + import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.dimension.end.EndDragonFight; +-import net.minecraft.world.level.levelgen.WorldGenSettings; + import net.minecraft.world.level.levelgen.WorldOptions; + import net.minecraft.world.level.timers.TimerCallbacks; + import net.minecraft.world.level.timers.TimerQueue; + import org.slf4j.Logger; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.NbtUtils; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; ++import net.minecraft.world.level.levelgen.WorldDimensions; ++import net.minecraft.world.level.levelgen.WorldGenSettings; ++import org.bukkit.Bukkit; ++import org.bukkit.event.weather.ThunderChangeEvent; ++import org.bukkit.event.weather.WeatherChangeEvent; ++// CraftBukkit end + + public class PrimaryLevelData implements ServerLevelData, WorldData { + +@@ -79,7 +90,21 @@ + private boolean wasModded; + private final Set removedFeatureFlags; + private final TimerQueue scheduledEvents; ++ // CraftBukkit start - Add world and pdc ++ public Registry customDimensions; ++ private ServerLevel world; ++ protected Tag pdc; + ++ public void setWorld(ServerLevel world) { ++ if (this.world != null) { ++ return; ++ } ++ this.world = world; ++ world.getWorld().readBukkitValues(this.pdc); ++ this.pdc = null; ++ } ++ // CraftBukkit end ++ + private PrimaryLevelData(@Nullable CompoundTag playerData, boolean modded, BlockPos spawnPos, float spawnAngle, long time, long timeOfDay, int version, int clearWeatherTime, int rainTime, boolean raining, int thunderTime, boolean thundering, boolean initialized, boolean difficultyLocked, WorldBorder.Settings worldBorder, int wanderingTraderSpawnDelay, int wanderingTraderSpawnChance, @Nullable UUID wanderingTraderId, Set serverBrands, Set removedFeatures, TimerQueue scheduledEvents, @Nullable CompoundTag customBossEvents, EndDragonFight.Data dragonFight, LevelSettings levelInfo, WorldOptions generatorOptions, PrimaryLevelData.SpecialWorldProperty specialProperty, Lifecycle lifecycle) { + this.wasModded = modded; + this.spawnPos = spawnPos; +@@ -116,7 +141,7 @@ + + public static PrimaryLevelData parse(Dynamic dynamic, LevelSettings info, PrimaryLevelData.SpecialWorldProperty specialProperty, WorldOptions generatorOptions, Lifecycle lifecycle) { + long i = dynamic.get("Time").asLong(0L); +- OptionalDynamic optionaldynamic = dynamic.get("Player"); ++ OptionalDynamic optionaldynamic = dynamic.get("Player"); // CraftBukkit - decompile error + Codec codec = CompoundTag.CODEC; + + Objects.requireNonNull(codec); +@@ -136,7 +161,7 @@ + WorldBorder.Settings worldborder_c = WorldBorder.Settings.read(dynamic, WorldBorder.DEFAULT_SETTINGS); + int k1 = dynamic.get("WanderingTraderSpawnDelay").asInt(0); + int l1 = dynamic.get("WanderingTraderSpawnChance").asInt(0); +- UUID uuid = (UUID) dynamic.get("WanderingTraderId").read(UUIDUtil.CODEC).result().orElse((Object) null); ++ UUID uuid = (UUID) dynamic.get("WanderingTraderId").read(UUIDUtil.CODEC).result().orElse(null); // CraftBukkit - decompile error + Set set = (Set) dynamic.get("ServerBrands").asStream().flatMap((dynamic1) -> { + return dynamic1.asString().result().stream(); + }).collect(Collectors.toCollection(Sets::newLinkedHashSet)); +@@ -145,7 +170,7 @@ + }).collect(Collectors.toSet()); + TimerQueue customfunctioncallbacktimerqueue = new TimerQueue<>(TimerCallbacks.SERVER_CALLBACKS, dynamic.get("ScheduledEvents").asStream()); + CompoundTag nbttagcompound1 = (CompoundTag) dynamic.get("CustomBossEvents").orElseEmptyMap().getValue(); +- DataResult dataresult = dynamic.get("DragonFight").read(EndDragonFight.Data.CODEC); ++ DataResult dataresult = dynamic.get("DragonFight").read(EndDragonFight.Data.CODEC); // CraftBukkit - decompile error + Logger logger = PrimaryLevelData.LOGGER; + + Objects.requireNonNull(logger); +@@ -180,7 +205,7 @@ + levelNbt.put("Version", nbttagcompound2); + NbtUtils.addCurrentDataVersion(levelNbt); + DynamicOps dynamicops = registryManager.createSerializationContext(NbtOps.INSTANCE); +- DataResult dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, registryManager); ++ DataResult dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, new WorldDimensions(this.customDimensions != null ? this.customDimensions : registryManager.lookupOrThrow(Registries.LEVEL_STEM))); // CraftBukkit + Logger logger = PrimaryLevelData.LOGGER; + + Objects.requireNonNull(logger); +@@ -230,11 +255,13 @@ + levelNbt.putUUID("WanderingTraderId", this.wanderingTraderId); + } + ++ levelNbt.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit ++ this.world.getWorld().storeBukkitValues(levelNbt); // CraftBukkit - add pdc + } + + private static ListTag stringCollectionToTag(Set strings) { + ListTag nbttaglist = new ListTag(); +- Stream stream = strings.stream().map(StringTag::valueOf); ++ Stream stream = strings.stream().map(StringTag::valueOf); // CraftBukkit - decompile error + + Objects.requireNonNull(nbttaglist); + stream.forEach(nbttaglist::add); +@@ -310,6 +337,25 @@ + + @Override + public void setThundering(boolean thundering) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); ++ } ++ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents ++ // CraftBukkit start ++ if (this.thundering == thundering) { ++ return; ++ } ++ ++ org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); ++ if (world != null) { ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents ++ Bukkit.getServer().getPluginManager().callEvent(thunder); ++ if (thunder.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + this.thundering = thundering; + } + +@@ -330,6 +376,26 @@ + + @Override + public void setRaining(boolean raining) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); ++ } ++ ++ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents ++ // CraftBukkit start ++ if (this.raining == raining) { ++ return; ++ } ++ ++ org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); ++ if (world != null) { ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents ++ Bukkit.getServer().getPluginManager().callEvent(weather); ++ if (weather.isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + this.raining = raining; + } + +@@ -396,6 +462,12 @@ + @Override + public void setDifficulty(Difficulty difficulty) { + this.settings = this.settings.withDifficulty(difficulty); ++ // CraftBukkit start ++ ClientboundChangeDifficultyPacket packet = new ClientboundChangeDifficultyPacket(this.getDifficulty(), this.isDifficultyLocked()); ++ for (ServerPlayer player : (java.util.List) (java.util.List) this.world.players()) { ++ player.connection.send(packet); ++ } ++ // CraftBukkit end + } + + @Override +@@ -532,6 +604,14 @@ + return this.settings.copy(); + } + ++ // CraftBukkit start - Check if the name stored in NBT is the correct one ++ public void checkName(String name) { ++ if (!this.settings.levelName.equals(name)) { ++ this.settings.levelName = name; ++ } ++ } ++ // CraftBukkit end ++ + /** @deprecated */ + @Deprecated + public static enum SpecialWorldProperty { diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch new file mode 100644 index 0000000000..c9f13ba298 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/level/storage/loot/LootDataType.java ++++ b/net/minecraft/world/level/storage/loot/LootDataType.java +@@ -9,6 +9,11 @@ + import net.minecraft.world.level.storage.loot.functions.LootItemFunctions; + import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.CraftLootTable; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++// CraftBukkit end ++ + public record LootDataType(ResourceKey> registryKey, Codec codec, LootDataType.Validator validator) { + + public static final LootDataType PREDICATE = new LootDataType<>(Registries.PREDICATE, LootItemCondition.DIRECT_CODEC, createSimpleValidator()); +@@ -32,6 +37,7 @@ + private static LootDataType.Validator createLootTableValidator() { + return (lootcollector, resourcekey, loottable) -> { + loottable.validate(lootcollector.setContextKeySet(loottable.getParamSet()).enterElement("{" + String.valueOf(resourcekey.registry()) + "/" + String.valueOf(resourcekey.location()) + "}", resourcekey)); ++ loottable.craftLootTable = new CraftLootTable(CraftNamespacedKey.fromMinecraft(resourcekey.location()), loottable); // CraftBukkit + }; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch new file mode 100644 index 0000000000..29133b2303 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch @@ -0,0 +1,85 @@ +--- a/net/minecraft/world/level/storage/loot/LootTable.java ++++ b/net/minecraft/world/level/storage/loot/LootTable.java +@@ -31,6 +31,13 @@ + import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.craftbukkit.CraftLootTable; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.event.world.LootGenerateEvent; ++// CraftBukkit end ++ + public class LootTable { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -54,6 +61,7 @@ + private final List pools; + private final List functions; + private final BiFunction compositeFunction; ++ public CraftLootTable craftLootTable; // CraftBukkit + + LootTable(ContextKeySet type, Optional randomSequenceId, List pools, List functions) { + this.paramSet = type; +@@ -64,9 +72,10 @@ + } + + public static Consumer createStackSplitter(ServerLevel world, Consumer consumer) { ++ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items + return (itemstack) -> { + if (itemstack.isItemEnabled(world.enabledFeatures())) { +- if (itemstack.getCount() < itemstack.getMaxStackSize()) { ++ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items + consumer.accept(itemstack); + } else { + int i = itemstack.getCount(); +@@ -157,10 +166,23 @@ + } + + public void fill(Container inventory, LootParams parameters, long seed) { +- LootContext loottableinfo = (new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence); ++ // CraftBukkit start ++ this.fillInventory(inventory, parameters, seed, false); ++ } ++ ++ public void fillInventory(Container iinventory, LootParams lootparams, long i, boolean plugin) { ++ // CraftBukkit end ++ LootContext loottableinfo = (new LootContext.Builder(lootparams)).withOptionalRandomSeed(i).create(this.randomSequence); + ObjectArrayList objectarraylist = this.getRandomItems(loottableinfo); + RandomSource randomsource = loottableinfo.getRandom(); +- List list = this.getAvailableSlots(inventory, randomsource); ++ // CraftBukkit start ++ LootGenerateEvent event = CraftEventFactory.callLootGenerateEvent(iinventory, this, loottableinfo, objectarraylist, plugin); ++ if (event.isCancelled()) { ++ return; ++ } ++ objectarraylist = event.getLoot().stream().map(CraftItemStack::asNMSCopy).collect(ObjectArrayList.toList()); ++ // CraftBukkit end ++ List list = this.getAvailableSlots(iinventory, randomsource); + + this.shuffleAndSplitItems(objectarraylist, list.size(), randomsource); + ObjectListIterator objectlistiterator = objectarraylist.iterator(); +@@ -174,9 +196,9 @@ + } + + if (itemstack.isEmpty()) { +- inventory.setItem((Integer) list.remove(list.size() - 1), ItemStack.EMPTY); ++ iinventory.setItem((Integer) list.remove(list.size() - 1), ItemStack.EMPTY); + } else { +- inventory.setItem((Integer) list.remove(list.size() - 1), itemstack); ++ iinventory.setItem((Integer) list.remove(list.size() - 1), itemstack); + } + } + +@@ -238,8 +260,8 @@ + + public static class Builder implements FunctionUserBuilder { + +- private final Builder pools = ImmutableList.builder(); +- private final Builder functions = ImmutableList.builder(); ++ private final ImmutableList.Builder pools = ImmutableList.builder(); ++ private final ImmutableList.Builder functions = ImmutableList.builder(); + private ContextKeySet paramSet; + private Optional randomSequence; + diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch new file mode 100644 index 0000000000..f97db2bfbc --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java ++++ b/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java +@@ -126,9 +126,35 @@ + protected abstract class EntryBase implements LootPoolEntry { + @Override + public int getWeight(float luck) { +- return Math.max(Mth.floor((float)LootPoolSingletonContainer.this.weight + (float)LootPoolSingletonContainer.this.quality * luck), 0); ++ // Paper start - Configurable LootPool luck formula ++ // SEE: https://luckformula.emc.gs for details and data ++ if (LootPoolSingletonContainer.this.lastLuck != null && LootPoolSingletonContainer.this.lastLuck == luck) { ++ return lastWeight; ++ } ++ // This is vanilla ++ float qualityModifer = (float) LootPoolSingletonContainer.this.quality * luck; ++ double baseWeight = (LootPoolSingletonContainer.this.weight + qualityModifer); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useAlternativeLuckFormula) { ++ // Random boost to avoid losing precision in the final int cast on return ++ final int weightBoost = 100; ++ baseWeight *= weightBoost; ++ // If we have vanilla 1, bump that down to 0 so nothing is is impacted ++ // vanilla 3 = 300, 200 basis = impact 2% ++ // =($B2*(($B2-100)/100/100)) ++ double impacted = baseWeight * ((baseWeight - weightBoost) / weightBoost / 100); ++ // =($B$7/100) ++ float luckModifier = Math.min(100, luck * 10) / 100; ++ // =B2 - (C2 *($B$7/100)) ++ baseWeight = Math.ceil(baseWeight - (impacted * luckModifier)); ++ } ++ LootPoolSingletonContainer.this.lastLuck = luck; ++ LootPoolSingletonContainer.this.lastWeight = (int) Math.max(Math.floor(baseWeight), 0); ++ return lastWeight; + } + } ++ private Float lastLuck = null; ++ private int lastWeight = 0; ++ // Paper end - Configurable LootPool luck formula + + @FunctionalInterface + protected interface EntryConstructor { diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch new file mode 100644 index 0000000000..130a1b59c6 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java ++++ b/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +@@ -83,8 +83,17 @@ + Vec3 vec3 = context.getOptionalParameter(LootContextParams.ORIGIN); + if (vec3 != null) { + ServerLevel serverLevel = context.getLevel(); ++ // Paper start - Configurable cartographer treasure maps ++ if (!serverLevel.paperConfig().environment.treasureMaps.enabled) { ++ /* ++ * NOTE: I fear users will just get a plain map as their "treasure" ++ * This is preferable to disrespecting the config. ++ */ ++ return stack; ++ } ++ // Paper end - Configurable cartographer treasure maps + BlockPos blockPos = serverLevel.findNearestMapStructure( +- this.destination, BlockPos.containing(vec3), this.searchRadius, this.skipKnownStructures ++ this.destination, BlockPos.containing(vec3), this.searchRadius, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredLootTable.or(!this.skipKnownStructures) // Paper - Configurable cartographer treasure maps + ); + if (blockPos != null) { + ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), this.zoom, true, true); diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch new file mode 100644 index 0000000000..19cd841992 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java ++++ b/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java +@@ -31,7 +31,8 @@ + RandomSource randomsource = loottableinfo.getRandom(); + float f = 1.0F / ofloat; + +- return randomsource.nextFloat() <= f; ++ // CraftBukkit - <= to < to allow for plugins to completely disable block drops from explosions ++ return randomsource.nextFloat() < f; + } else { + return true; + } diff --git a/paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch b/paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch new file mode 100644 index 0000000000..d29c6da396 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/scores/ScoreboardSaveData.java ++++ b/net/minecraft/world/scores/ScoreboardSaveData.java +@@ -148,6 +148,7 @@ + ListTag listTag = new ListTag(); + + for (PlayerTeam playerTeam : this.scoreboard.getPlayerTeams()) { ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().scoreboards.saveEmptyScoreboardTeams && playerTeam.getPlayers().isEmpty()) continue; // Paper - Don't save empty scoreboard teams to scoreboard.dat + CompoundTag compoundTag = new CompoundTag(); + compoundTag.putString("Name", playerTeam.getName()); + compoundTag.putString("DisplayName", Component.Serializer.toJson(playerTeam.getDisplayName(), registries)); diff --git a/paper-server/src/assembly/META-INF/main-class b/paper-server/src/assembly/META-INF/main-class new file mode 100644 index 0000000000..94a6b1554c --- /dev/null +++ b/paper-server/src/assembly/META-INF/main-class @@ -0,0 +1 @@ +org.bukkit.craftbukkit.Main \ No newline at end of file diff --git a/paper-server/src/assembly/bootstrap.xml b/paper-server/src/assembly/bootstrap.xml new file mode 100644 index 0000000000..f8980991f5 --- /dev/null +++ b/paper-server/src/assembly/bootstrap.xml @@ -0,0 +1,65 @@ + + + bootstrap + + + jar + + + false + + + + libraries.list + META-INF + ${project.build.directory}/dependencies-checksums.sha + + + versions.list + META-INF + ${project.build.directory}/artifacts-checksums.sha + + + + + + ${project.basedir}/src/assembly/META-INF + true + META-INF + + + + + + + + org.spigotmc:minecraft-server + + META-INF/libraries + false + + + + + + ${project.groupId}:${project.artifactId} + + META-INF/versions + + + + + + ${project.groupId}:${project.artifactId} + + true + + + org/bukkit/craftbukkit/bootstrap/** + + + + + diff --git a/paper-server/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java b/paper-server/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java new file mode 100644 index 0000000000..91547f6e6f --- /dev/null +++ b/paper-server/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java @@ -0,0 +1,51 @@ +package io.papermc.paper.console; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.core.pattern.PatternFormatter; +import org.apache.logging.log4j.core.pattern.PatternParser; + +import java.util.List; +import java.util.regex.Pattern; + +@Plugin(name = "stripAnsi", category = PatternConverter.CATEGORY) +@ConverterKeys({"stripAnsi"}) +public final class StripANSIConverter extends LogEventPatternConverter { + final private Pattern ANSI_PATTERN = Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); + + private final List formatters; + + private StripANSIConverter(List formatters) { + super("stripAnsi", null); + this.formatters = formatters; + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + int start = toAppendTo.length(); + for (PatternFormatter formatter : formatters) { + formatter.format(event, toAppendTo); + } + String content = toAppendTo.substring(start); + content = ANSI_PATTERN.matcher(content).replaceAll(""); + + toAppendTo.setLength(start); + toAppendTo.append(content); + } + + public static StripANSIConverter newInstance(Configuration config, String[] options) { + if (options.length != 1) { + LOGGER.error("Incorrect number of options on stripAnsi. Expected exactly 1, received " + options.length); + return null; + } + + PatternParser parser = PatternLayout.createPatternParser(config); + List formatters = parser.parse(options[0]); + return new StripANSIConverter(formatters); + } +} diff --git a/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java new file mode 100644 index 0000000000..6ffd1befe6 --- /dev/null +++ b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java @@ -0,0 +1,130 @@ +package io.papermc.paper.logging; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.ReadOnlyStringMap; + +import java.util.Map; + +public class DelegateLogEvent implements LogEvent { + private final LogEvent original; + + protected DelegateLogEvent(LogEvent original) { + this.original = original; + } + + @Override + public LogEvent toImmutable() { + return this.original.toImmutable(); + } + + @Override + public Map getContextMap() { + return this.original.getContextMap(); + } + + @Override + public ReadOnlyStringMap getContextData() { + return this.original.getContextData(); + } + + @Override + public ThreadContext.ContextStack getContextStack() { + return this.original.getContextStack(); + } + + @Override + public String getLoggerFqcn() { + return this.original.getLoggerFqcn(); + } + + @Override + public Level getLevel() { + return this.original.getLevel(); + } + + @Override + public String getLoggerName() { + return this.original.getLoggerName(); + } + + @Override + public Marker getMarker() { + return this.original.getMarker(); + } + + @Override + public Message getMessage() { + return this.original.getMessage(); + } + + @Override + public long getTimeMillis() { + return this.original.getTimeMillis(); + } + + @Override + public Instant getInstant() { + return this.original.getInstant(); + } + + @Override + public StackTraceElement getSource() { + return this.original.getSource(); + } + + @Override + public String getThreadName() { + return this.original.getThreadName(); + } + + @Override + public long getThreadId() { + return this.original.getThreadId(); + } + + @Override + public int getThreadPriority() { + return this.original.getThreadPriority(); + } + + @Override + public Throwable getThrown() { + return this.original.getThrown(); + } + + @Override + public ThrowableProxy getThrownProxy() { + return this.original.getThrownProxy(); + } + + @Override + public boolean isEndOfBatch() { + return this.original.isEndOfBatch(); + } + + @Override + public boolean isIncludeLocation() { + return this.original.isIncludeLocation(); + } + + @Override + public void setEndOfBatch(boolean endOfBatch) { + this.original.setEndOfBatch(endOfBatch); + } + + @Override + public void setIncludeLocation(boolean locationRequired) { + this.original.setIncludeLocation(locationRequired); + } + + @Override + public long getNanoTime() { + return this.original.getNanoTime(); + } +} diff --git a/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java new file mode 100644 index 0000000000..558427c65b --- /dev/null +++ b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java @@ -0,0 +1,48 @@ +package io.papermc.paper.logging; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.ExtendedClassInfo; +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; +import org.apache.logging.log4j.core.impl.ThrowableProxy; + +public class ExtraClassInfoLogEvent extends DelegateLogEvent { + + private boolean fixed; + + public ExtraClassInfoLogEvent(LogEvent original) { + super(original); + } + + @Override + public ThrowableProxy getThrownProxy() { + if (fixed) { + return super.getThrownProxy(); + } + rewriteStackTrace(super.getThrownProxy()); + fixed = true; + return super.getThrownProxy(); + } + + private void rewriteStackTrace(ThrowableProxy throwable) { + ExtendedStackTraceElement[] stackTrace = throwable.getExtendedStackTrace(); + for (int i = 0; i < stackTrace.length; i++) { + ExtendedClassInfo classInfo = stackTrace[i].getExtraClassInfo(); + if (classInfo.getLocation().equals("?")) { + StackTraceElement element = stackTrace[i].getStackTraceElement(); + String classLoaderName = element.getClassLoaderName(); + if (classLoaderName != null) { + stackTrace[i] = new ExtendedStackTraceElement(element, + new ExtendedClassInfo(classInfo.getExact(), classLoaderName, "?")); + } + } + } + if (throwable.getCauseProxy() != null) { + rewriteStackTrace(throwable.getCauseProxy()); + } + if (throwable.getSuppressedProxies() != null) { + for (ThrowableProxy proxy : throwable.getSuppressedProxies()) { + rewriteStackTrace(proxy); + } + } + } +} diff --git a/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java new file mode 100644 index 0000000000..34734bb969 --- /dev/null +++ b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java @@ -0,0 +1,29 @@ +package io.papermc.paper.logging; + +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.jetbrains.annotations.NotNull; + +@Plugin( + name = "ExtraClassInfoRewritePolicy", + category = Core.CATEGORY_NAME, + elementType = "rewritePolicy", + printObject = true +) +public final class ExtraClassInfoRewritePolicy implements RewritePolicy { + @Override + public LogEvent rewrite(LogEvent source) { + if (source.getThrown() != null) { + return new ExtraClassInfoLogEvent(source); + } + return source; + } + + @PluginFactory + public static @NotNull ExtraClassInfoRewritePolicy createPolicy() { + return new ExtraClassInfoRewritePolicy(); + } +} diff --git a/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java new file mode 100644 index 0000000000..66b6011ee3 --- /dev/null +++ b/paper-server/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java @@ -0,0 +1,66 @@ +package io.papermc.paper.logging; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.checkerframework.checker.nullness.qual.NonNull; + +@Plugin( + name = "StacktraceDeobfuscatingRewritePolicy", + category = Core.CATEGORY_NAME, + elementType = "rewritePolicy", + printObject = true +) +public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { + private static final MethodHandle DEOBFUSCATE_THROWABLE; + + static { + try { + final Class cls = Class.forName("io.papermc.paper.util.StacktraceDeobfuscator"); + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + final VarHandle instanceHandle = lookup.findStaticVarHandle(cls, "INSTANCE", cls); + final Object deobfuscator = instanceHandle.get(); + DEOBFUSCATE_THROWABLE = lookup + .unreflect(cls.getDeclaredMethod("deobfuscateThrowable", Throwable.class)) + .bindTo(deobfuscator); + } catch (final ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } + + private StacktraceDeobfuscatingRewritePolicy() { + } + + @Override + public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) { + final Throwable thrown = rewrite.getThrown(); + if (thrown != null) { + deobfuscateThrowable(thrown); + return new Log4jLogEvent.Builder(rewrite) + .setThrownProxy(null) + .build(); + } + return rewrite; + } + + private static void deobfuscateThrowable(final Throwable thrown) { + try { + DEOBFUSCATE_THROWABLE.invoke(thrown); + } catch (final Error e) { + throw e; + } catch (final Throwable e) { + throw new RuntimeException(e); + } + } + + @PluginFactory + public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() { + return new StacktraceDeobfuscatingRewritePolicy(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java new file mode 100644 index 0000000000..6c98d420ea --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java @@ -0,0 +1,117 @@ +package ca.spottedleaf.moonrise.common; + +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.DataFixer; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.phys.AABB; +import java.util.List; +import java.util.ServiceLoader; +import java.util.function.Predicate; + +public interface PlatformHooks { + public static PlatformHooks get() { + return Holder.INSTANCE; + } + + public String getBrand(); + + public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); + + public Predicate maybeHasLightEmission(); + + public boolean hasCurrentlyLoadingChunk(); + + public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); + + public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); + + public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); + + public boolean allowAsyncTicketUpdates(); + + public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); + + public void chunkUnloadFromWorld(final LevelChunk chunk); + + public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); + + public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); + + public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); + + public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, + final List into); + + public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, + final AABB boundingBox, final Predicate predicate, + final List into, final int maxCount); + + public void entityMove(final Entity entity, final long oldSection, final long newSection); + + public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); + + public boolean configFixMC224294(); + + public boolean configAutoConfigSendDistance(); + + public double configPlayerMaxLoadRate(); + + public double configPlayerMaxGenRate(); + + public double configPlayerMaxSendRate(); + + public int configPlayerMaxConcurrentLoads(); + + public int configPlayerMaxConcurrentGens(); + + public long configAutoSaveInterval(final ServerLevel world); + + public int configMaxAutoSavePerTick(final ServerLevel world); + + public boolean configFixMC159283(); + + // support for CB chunk mustNotSave + public boolean forceNoSave(final ChunkAccess chunk); + + public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, + final int fromVersion, final int toVersion); + + public boolean hasMainChunkLoadHook(); + + public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); + + public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); + + public void unloadEntity(final Entity entity); + + public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk); + + public int modifyEntityTrackingRange(final Entity entity, final int currentRange); + + public static final class Holder { + private Holder() { + } + + private static final PlatformHooks INSTANCE; + + static { + INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() + .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java new file mode 100644 index 0000000000..7fed43a1e7 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java @@ -0,0 +1,128 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import net.minecraft.world.entity.Entity; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// list with O(1) remove & contains + +/** + * @author Spottedleaf + */ +public final class EntityList implements Iterable { + + private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + private static final Entity[] EMPTY_LIST = new Entity[0]; + + private Entity[] entities = EMPTY_LIST; + private int count; + + public int size() { + return this.count; + } + + public boolean contains(final Entity entity) { + return this.entityToIndex.containsKey(entity.getId()); + } + + public boolean remove(final Entity entity) { + final int index = this.entityToIndex.remove(entity.getId()); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entity at the end to this index + final int endIndex = --this.count; + final Entity end = this.entities[endIndex]; + if (index != endIndex) { + // not empty after this call + this.entityToIndex.put(end.getId(), index); // update index + } + this.entities[index] = end; + this.entities[endIndex] = null; + + return true; + } + + public boolean add(final Entity entity) { + final int count = this.count; + final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + Entity[] list = this.entities; + + if (list.length == count) { + // resize required + list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = entity; + this.count = count + 1; + + return true; + } + + public Entity getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.entities[index]; + } + + public Entity getUnchecked(final int index) { + return this.entities[index]; + } + + public Entity[] getRawData() { + return this.entities; + } + + public void clear() { + this.entityToIndex.clear(); + Arrays.fill(this.entities, 0, this.count, null); + this.count = 0; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + private Entity lastRet; + private int current; + + @Override + public boolean hasNext() { + return this.current < EntityList.this.count; + } + + @Override + public Entity next() { + if (this.current >= EntityList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = EntityList.this.entities[this.current++]; + } + + @Override + public void remove() { + final Entity lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + EntityList.this.remove(lastRet); + --this.current; + } + }; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java new file mode 100644 index 0000000000..9f3b25bb24 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java @@ -0,0 +1,77 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import java.util.Arrays; + +public final class IntList { + + private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); + { + this.map.defaultReturnValue(Integer.MIN_VALUE); + } + + private static final int[] EMPTY_LIST = new int[0]; + + private int[] byIndex = EMPTY_LIST; + private int count; + + public int size() { + return this.count; + } + + public void setMinCapacity(final int len) { + final int[] byIndex = this.byIndex; + if (byIndex.length < len) { + this.byIndex = Arrays.copyOf(byIndex, len); + } + } + + public int getRaw(final int index) { + return this.byIndex[index]; + } + + public boolean add(final int value) { + final int count = this.count; + final int currIndex = this.map.putIfAbsent(value, count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + int[] list = this.byIndex; + + if (list.length == count) { + // resize required + list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = value; + this.count = count + 1; + + return true; + } + + public boolean remove(final int value) { + final int index = this.map.remove(value); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entry at the end to this index + final int endIndex = --this.count; + final int end = this.byIndex[endIndex]; + if (index != endIndex) { + // not empty after this call + this.map.put(end, index); + } + this.byIndex[index] = end; + this.byIndex[endIndex] = 0; + + return true; + } + + public void clear() { + this.count = 0; + this.map.clear(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java new file mode 100644 index 0000000000..c21e00812f --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java @@ -0,0 +1,312 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import java.util.Arrays; +import java.util.NoSuchElementException; + +public final class IteratorSafeOrderedReferenceSet { + + public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0; + + private final Reference2IntLinkedOpenHashMap indexMap; + private int firstInvalidIndex = -1; + + /* list impl */ + private E[] listElements; + private int listSize; + + private final double maxFragFactor; + + private int iteratorCount; + + public IteratorSafeOrderedReferenceSet() { + this(16, 0.75f, 16, 0.2); + } + + public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, + final double maxFragFactor) { + this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor); + this.indexMap.defaultReturnValue(-1); + this.maxFragFactor = maxFragFactor; + this.listElements = (E[])new Object[arrayCapacity]; + } + + /* + public void check() { + int iterated = 0; + ReferenceOpenHashSet check = new ReferenceOpenHashSet<>(); + if (this.listElements != null) { + for (int i = 0; i < this.listSize; ++i) { + Object obj = this.listElements[i]; + if (obj != null) { + iterated++; + if (!check.add((E)obj)) { + throw new IllegalStateException("contains duplicate"); + } + if (!this.contains((E)obj)) { + throw new IllegalStateException("desync"); + } + } + } + } + + if (iterated != this.size()) { + throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size()); + } + + check.clear(); + iterated = 0; + for (final java.util.Iterator iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { + final E element = iterator.next(); + iterated++; + if (!check.add(element)) { + throw new IllegalStateException("contains duplicate (iterator is wrong)"); + } + if (!this.contains(element)) { + throw new IllegalStateException("desync (iterator is wrong)"); + } + } + + if (iterated != this.size()) { + throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size()); + } + } + */ + + private double getFragFactor() { + return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); + } + + public int createRawIterator() { + ++this.iteratorCount; + if (this.indexMap.isEmpty()) { + return -1; + } else { + return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; + } + } + + public int advanceRawIterator(final int index) { + final E[] elements = this.listElements; + int ret = index + 1; + for (int len = this.listSize; ret < len; ++ret) { + if (elements[ret] != null) { + return ret; + } + } + + return -1; + } + + public void finishRawIterator() { + if (--this.iteratorCount == 0) { + if (this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); + } + } + } + + public boolean remove(final E element) { + final int index = this.indexMap.removeInt(element); + if (index >= 0) { + if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { + this.firstInvalidIndex = index; + } + if (this.listElements[index] != element) { + throw new IllegalStateException(); + } + this.listElements[index] = null; + if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); + } + //this.check(); + return true; + } + return false; + } + + public boolean contains(final E element) { + return this.indexMap.containsKey(element); + } + + public boolean add(final E element) { + final int listSize = this.listSize; + + final int previous = this.indexMap.putIfAbsent(element, listSize); + if (previous != -1) { + return false; + } + + if (listSize >= this.listElements.length) { + this.listElements = Arrays.copyOf(this.listElements, listSize * 2); + } + this.listElements[listSize] = element; + this.listSize = listSize + 1; + + //this.check(); + return true; + } + + private void defrag() { + if (this.firstInvalidIndex < 0) { + return; // nothing to do + } + + if (this.indexMap.isEmpty()) { + Arrays.fill(this.listElements, 0, this.listSize, null); + this.listSize = 0; + this.firstInvalidIndex = -1; + //this.check(); + return; + } + + final E[] backingArray = this.listElements; + + int lastValidIndex; + java.util.Iterator> iterator; + + if (this.firstInvalidIndex == 0) { + iterator = this.indexMap.reference2IntEntrySet().fastIterator(); + lastValidIndex = 0; + } else { + lastValidIndex = this.firstInvalidIndex; + final E key = backingArray[lastValidIndex - 1]; + iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { + @Override + public int getIntValue() { + throw new UnsupportedOperationException(); + } + + @Override + public int setValue(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public E getKey() { + return key; + } + }); + } + + while (iterator.hasNext()) { + final Reference2IntMap.Entry entry = iterator.next(); + + final int newIndex = lastValidIndex++; + backingArray[newIndex] = entry.getKey(); + entry.setValue(newIndex); + } + + // cleanup end + Arrays.fill(backingArray, lastValidIndex, this.listSize, null); + this.listSize = lastValidIndex; + this.firstInvalidIndex = -1; + //this.check(); + } + + public E rawGet(final int index) { + return this.listElements[index]; + } + + public int size() { + // always returns the correct amount - listSize can be different + return this.indexMap.size(); + } + + public IteratorSafeOrderedReferenceSet.Iterator iterator() { + return this.iterator(0); + } + + public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { + ++this.iteratorCount; + return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); + } + + public java.util.Iterator unsafeIterator() { + return this.unsafeIterator(0); + } + public java.util.Iterator unsafeIterator(final int flags) { + return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); + } + + public static interface Iterator extends java.util.Iterator { + + public void finishedIterating(); + + } + + private static final class BaseIterator implements IteratorSafeOrderedReferenceSet.Iterator { + + private final IteratorSafeOrderedReferenceSet set; + private final boolean canFinish; + private final int maxIndex; + private int nextIndex; + private E pendingValue; + private boolean finished; + private E lastReturned; + + private BaseIterator(final IteratorSafeOrderedReferenceSet set, final boolean canFinish, final int maxIndex) { + this.set = set; + this.canFinish = canFinish; + this.maxIndex = maxIndex; + } + + @Override + public boolean hasNext() { + if (this.finished) { + return false; + } + if (this.pendingValue != null) { + return true; + } + + final E[] elements = this.set.listElements; + int index, len; + for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) { + final E element = elements[index]; + if (element != null) { + this.pendingValue = element; + this.nextIndex = index + 1; + return true; + } + } + + this.nextIndex = index; + return false; + } + + @Override + public E next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + final E ret = this.pendingValue; + + this.pendingValue = null; + this.lastReturned = ret; + + return ret; + } + + @Override + public void remove() { + final E lastReturned = this.lastReturned; + if (lastReturned == null) { + throw new IllegalStateException(); + } + this.lastReturned = null; + this.set.remove(lastReturned); + } + + @Override + public void finishedIterating() { + if (this.finished || !this.canFinish) { + throw new IllegalStateException(); + } + this.lastReturned = null; + this.finished = true; + this.set.finishRawIterator(); + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java new file mode 100644 index 0000000000..2e876b9186 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java @@ -0,0 +1,142 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class ReferenceList implements Iterable { + + private static final Object[] EMPTY_LIST = new Object[0]; + + private final Reference2IntOpenHashMap referenceToIndex; + private E[] references; + private int count; + + public ReferenceList() { + this((E[])EMPTY_LIST); + } + + public ReferenceList(final E[] referenceArray) { + this.references = referenceArray; + this.referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f); + this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + private ReferenceList(final E[] references, final int count, final Reference2IntOpenHashMap referenceToIndex) { + this.references = references; + this.count = count; + this.referenceToIndex = referenceToIndex; + } + + public ReferenceList copy() { + return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone()); + } + + public int size() { + return this.count; + } + + public boolean contains(final E obj) { + return this.referenceToIndex.containsKey(obj); + } + + public boolean remove(final E obj) { + final int index = this.referenceToIndex.removeInt(obj); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the object at the end to this index + final int endIndex = --this.count; + final E end = (E)this.references[endIndex]; + if (index != endIndex) { + // not empty after this call + this.referenceToIndex.put(end, index); // update index + } + this.references[index] = end; + this.references[endIndex] = null; + + return true; + } + + public boolean add(final E obj) { + final int count = this.count; + final int currIndex = this.referenceToIndex.putIfAbsent(obj, count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + E[] list = this.references; + + if (list.length == count) { + // resize required + list = this.references = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = obj; + this.count = count + 1; + + return true; + } + + public E getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.references[index]; + } + + public E getUnchecked(final int index) { + return this.references[index]; + } + + public Object[] getRawData() { + return this.references; + } + + public E[] getRawDataUnchecked() { + return this.references; + } + + public void clear() { + this.referenceToIndex.clear(); + Arrays.fill(this.references, 0, this.count, null); + this.count = 0; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + private E lastRet; + private int current; + + @Override + public boolean hasNext() { + return this.current < ReferenceList.this.count; + } + + @Override + public E next() { + if (this.current >= ReferenceList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = ReferenceList.this.references[this.current++]; + } + + @Override + public void remove() { + final E lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + ReferenceList.this.remove(lastRet); + --this.current; + } + }; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java new file mode 100644 index 0000000000..2bae9949ef --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java @@ -0,0 +1,77 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import java.util.Arrays; + +public final class ShortList { + + private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); + { + this.map.defaultReturnValue(Short.MIN_VALUE); + } + + private static final short[] EMPTY_LIST = new short[0]; + + private short[] byIndex = EMPTY_LIST; + private short count; + + public int size() { + return (int)this.count; + } + + public short getRaw(final int index) { + return this.byIndex[index]; + } + + public void setMinCapacity(final int len) { + final short[] byIndex = this.byIndex; + if (byIndex.length < len) { + this.byIndex = Arrays.copyOf(byIndex, len); + } + } + + public boolean add(final short value) { + final int count = (int)this.count; + final short currIndex = this.map.putIfAbsent(value, (short)count); + + if (currIndex != Short.MIN_VALUE) { + return false; // already in this list + } + + short[] list = this.byIndex; + + if (list.length == count) { + // resize required + list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = value; + this.count = (short)(count + 1); + + return true; + } + + public boolean remove(final short value) { + final short index = this.map.remove(value); + if (index == Short.MIN_VALUE) { + return false; + } + + // move the entry at the end to this index + final short endIndex = --this.count; + final short end = this.byIndex[endIndex]; + if (index != endIndex) { + // not empty after this call + this.map.put(end, index); + } + this.byIndex[(int)index] = end; + this.byIndex[(int)endIndex] = (short)0; + + return true; + } + + public void clear() { + this.count = (short)0; + this.map.clear(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java new file mode 100644 index 0000000000..db92261a6c --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java @@ -0,0 +1,117 @@ +package ca.spottedleaf.moonrise.common.list; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Comparator; + +public final class SortedList { + + private static final Object[] EMPTY_LIST = new Object[0]; + + private Comparator comparator; + private E[] elements; + private int count; + + public SortedList(final Comparator comparator) { + this((E[])EMPTY_LIST, comparator); + } + + public SortedList(final E[] elements, final Comparator comparator) { + this.elements = elements; + this.comparator = comparator; + } + + // start, end are inclusive + private static int insertIdx(final E[] elements, final E element, final Comparator comparator, + int start, int end) { + while (start <= end) { + final int middle = (start + end) >>> 1; + + final E middleVal = elements[middle]; + + final int cmp = comparator.compare(element, middleVal); + + if (cmp < 0) { + end = middle - 1; + } else { + start = middle + 1; + } + } + + return start; + } + + public int size() { + return this.count; + } + + public boolean isEmpty() { + return this.count == 0; + } + + public int add(final E element) { + E[] elements = this.elements; + final int count = this.count; + this.count = count + 1; + final Comparator comparator = this.comparator; + + final int idx = insertIdx(elements, element, comparator, 0, count - 1); + + if (count >= elements.length) { + // copy and insert at the same time + if (idx == count) { + this.elements = elements = Arrays.copyOf(elements, (int)Math.max(4L, count * 2L)); // overflow results in negative + elements[count] = element; + return idx; + } else { + final E[] newElements = (E[])Array.newInstance(elements.getClass().getComponentType(), (int)Math.max(4L, count * 2L)); + System.arraycopy(elements, 0, newElements, 0, idx); + newElements[idx] = element; + System.arraycopy(elements, idx, newElements, idx + 1, count - idx); + this.elements = newElements; + return idx; + } + } else { + if (idx == count) { + // no copy needed + elements[idx] = element; + return idx; + } else { + // shift elements down + System.arraycopy(elements, idx, elements, idx + 1, count - idx); + elements[idx] = element; + return idx; + } + } + } + + public E get(final int idx) { + if (idx < 0 || idx >= this.count) { + throw new IndexOutOfBoundsException(idx); + } + return this.elements[idx]; + } + + + public E remove(final E element) { + E[] elements = this.elements; + final int count = this.count; + final Comparator comparator = this.comparator; + + final int idx = Arrays.binarySearch(elements, 0, count, element, comparator); + if (idx < 0) { + return null; + } + + final int last = this.count - 1; + this.count = last; + + final E ret = elements[idx]; + + System.arraycopy(elements, idx + 1, elements, idx, last - idx); + + elements[last] = null; + + return ret; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java new file mode 100644 index 0000000000..62caf61a4b --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java @@ -0,0 +1,77 @@ +package ca.spottedleaf.moonrise.common.map; + +import it.unimi.dsi.fastutil.ints.Int2IntFunction; + +import java.util.Arrays; + +public class Int2IntArraySortedMap { + + protected int[] key; + protected int[] val; + protected int size; + + public Int2IntArraySortedMap() { + this.key = new int[8]; + this.val = new int[8]; + } + + public int put(final int key, final int value) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + final int current = this.val[index]; + this.val[index] = value; + return current; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + this.val[insert] = value; + + return 0; + } + + public int computeIfAbsent(final int key, final Int2IntFunction producer) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + return this.val[index]; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + + return this.val[insert] = producer.apply(key); + } + + public int get(final int key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + return 0; + } + return this.val[index]; + } + + public int getFloor(final int key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + final int insert = -(index + 1) - 1; + return insert < 0 ? 0 : this.val[insert]; + } + return this.val[index]; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java new file mode 100644 index 0000000000..fea9e8ba7c --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java @@ -0,0 +1,74 @@ +package ca.spottedleaf.moonrise.common.map; + +import java.util.Arrays; +import java.util.function.IntFunction; + +public class Int2ObjectArraySortedMap { + + protected int[] key; + protected V[] val; + protected int size; + + public Int2ObjectArraySortedMap() { + this.key = new int[8]; + this.val = (V[])new Object[8]; + } + + public V put(final int key, final V value) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + final V current = this.val[index]; + this.val[index] = value; + return current; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + + this.key[insert] = key; + this.val[insert] = value; + + return null; + } + + public V computeIfAbsent(final int key, final IntFunction producer) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + return this.val[index]; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + + this.key[insert] = key; + + return this.val[insert] = producer.apply(key); + } + + public V get(final int key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + return null; + } + return this.val[index]; + } + + public V getFloor(final int key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + final int insert = -(index + 1); + return this.val[insert]; + } + return this.val[index]; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java new file mode 100644 index 0000000000..c077ca6069 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java @@ -0,0 +1,77 @@ +package ca.spottedleaf.moonrise.common.map; + +import it.unimi.dsi.fastutil.longs.Long2IntFunction; + +import java.util.Arrays; + +public class Long2IntArraySortedMap { + + protected long[] key; + protected int[] val; + protected int size; + + public Long2IntArraySortedMap() { + this.key = new long[8]; + this.val = new int[8]; + } + + public int put(final long key, final int value) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + final int current = this.val[index]; + this.val[index] = value; + return current; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + this.val[insert] = value; + + return 0; + } + + public int computeIfAbsent(final long key, final Long2IntFunction producer) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + return this.val[index]; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + + return this.val[insert] = producer.apply(key); + } + + public int get(final long key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + return 0; + } + return this.val[index]; + } + + public int getFloor(final long key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + final int insert = -(index + 1) - 1; + return insert < 0 ? 0 : this.val[insert]; + } + return this.val[index]; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java new file mode 100644 index 0000000000..b24d037af5 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java @@ -0,0 +1,76 @@ +package ca.spottedleaf.moonrise.common.map; + +import java.util.Arrays; +import java.util.function.LongFunction; + +public class Long2ObjectArraySortedMap { + + protected long[] key; + protected V[] val; + protected int size; + + public Long2ObjectArraySortedMap() { + this.key = new long[8]; + this.val = (V[])new Object[8]; + } + + public V put(final long key, final V value) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + final V current = this.val[index]; + this.val[index] = value; + return current; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + this.val[insert] = value; + + return null; + } + + public V computeIfAbsent(final long key, final LongFunction producer) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index >= 0) { + return this.val[index]; + } + final int insert = -(index + 1); + // shift entries down + if (this.size >= this.val.length) { + this.key = Arrays.copyOf(this.key, this.key.length * 2); + this.val = Arrays.copyOf(this.val, this.val.length * 2); + } + System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); + System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); + ++this.size; + + this.key[insert] = key; + + return this.val[insert] = producer.apply(key); + } + + public V get(final long key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + return null; + } + return this.val[index]; + } + + public V getFloor(final long key) { + final int index = Arrays.binarySearch(this.key, 0, this.size, key); + if (index < 0) { + final int insert = -(index + 1) - 1; + return insert < 0 ? null : this.val[insert]; + } + return this.val[index]; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java new file mode 100644 index 0000000000..aa86882bb7 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java @@ -0,0 +1,48 @@ +package ca.spottedleaf.moonrise.common.map; + +import it.unimi.dsi.fastutil.longs.Long2BooleanFunction; +import it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap; + +public final class SynchronisedLong2BooleanMap { + private final Long2BooleanLinkedOpenHashMap map = new Long2BooleanLinkedOpenHashMap(); + private final int limit; + + public SynchronisedLong2BooleanMap(final int limit) { + this.limit = limit; + } + + // must hold lock on map + private void purgeEntries() { + while (this.map.size() > this.limit) { + this.map.removeLastBoolean(); + } + } + + public boolean remove(final long key) { + synchronized (this.map) { + return this.map.remove(key); + } + } + + // note: + public boolean getOrCompute(final long key, final Long2BooleanFunction ifAbsent) { + synchronized (this.map) { + if (this.map.containsKey(key)) { + return this.map.getAndMoveToFirst(key); + } + } + + final boolean put = ifAbsent.get(key); + + synchronized (this.map) { + if (this.map.containsKey(key)) { + return this.map.getAndMoveToFirst(key); + } + this.map.putAndMoveToFirst(key, put); + + this.purgeEntries(); + + return put; + } + } +} \ No newline at end of file diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java new file mode 100644 index 0000000000..dbb51afc6c --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java @@ -0,0 +1,47 @@ +package ca.spottedleaf.moonrise.common.map; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import java.util.function.BiFunction; + +public final class SynchronisedLong2ObjectMap { + private final Long2ObjectLinkedOpenHashMap map = new Long2ObjectLinkedOpenHashMap<>(); + private final int limit; + + public SynchronisedLong2ObjectMap(final int limit) { + this.limit = limit; + } + + // must hold lock on map + private void purgeEntries() { + while (this.map.size() > this.limit) { + this.map.removeLast(); + } + } + + public V get(final long key) { + synchronized (this.map) { + return this.map.getAndMoveToFirst(key); + } + } + + public V put(final long key, final V value) { + synchronized (this.map) { + final V ret = this.map.putAndMoveToFirst(key, value); + this.purgeEntries(); + return ret; + } + } + + public V compute(final long key, final BiFunction remappingFunction) { + synchronized (this.map) { + // first, compute the value - if one is added, it will be at the last entry + this.map.compute(key, remappingFunction); + // move the entry to first, just in case it was added at last + final V ret = this.map.getAndMoveToFirst(key); + // now purge the last entries + this.purgeEntries(); + + return ret; + } + } +} \ No newline at end of file diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java new file mode 100644 index 0000000000..9c0eff9017 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java @@ -0,0 +1,75 @@ +package ca.spottedleaf.moonrise.common.misc; + +public final class AllocatingRateLimiter { + + // max difference granularity in ns + private final long maxGranularity; + + private double allocation = 0.0; + private long lastAllocationUpdate; + // the carry is used to store the remainder of the last take, so that the take amount remains the same (minus floating point error) + // over any time period using take regardless of the number of take calls or the intervals between the take calls + // i.e. take obtains 3.5 elements, stores 0.5 to this field for the next take() call to use and returns 3 + private double takeCarry = 0.0; + private long lastTakeUpdate; + + public AllocatingRateLimiter(final long maxGranularity) { + this.maxGranularity = maxGranularity; + } + + public void reset(final long time) { + this.allocation = 0.0; + this.lastAllocationUpdate = time; + this.takeCarry = 0.0; + this.lastTakeUpdate = time; + } + + // rate in units/s, and time in ns + public void tickAllocation(final long time, final double rate, final double maxAllocation) { + final long diff = Math.min(this.maxGranularity, time - this.lastAllocationUpdate); + this.lastAllocationUpdate = time; + + this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D)); + } + + public long previewAllocation(final long time, final double rate, final long maxTake) { + if (maxTake < 1L) { + return 0L; + } + + final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate); + + // note: abs(takeCarry) <= 1.0 + final double take = Math.min( + Math.min((double)maxTake - this.takeCarry, this.allocation), + rate * (diff*1.0E-9) + ); + + return (long)Math.floor(this.takeCarry + take); + } + + // rate in units/s, and time in ns + public long takeAllocation(final long time, final double rate, final long maxTake) { + if (maxTake < 1L) { + return 0L; + } + + double ret = this.takeCarry; + final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate); + this.lastTakeUpdate = time; + + // note: abs(takeCarry) <= 1.0 + final double take = Math.min( + Math.min((double)maxTake - this.takeCarry, this.allocation), + rate * (diff*1.0E-9) + ); + + ret += take; + this.allocation -= take; + + final long retInteger = (long)Math.floor(ret); + this.takeCarry = ret - (double)retInteger; + + return retInteger; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java new file mode 100644 index 0000000000..460e27ab05 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java @@ -0,0 +1,297 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; + +public final class Delayed26WayDistancePropagator3D { + + // this map is considered "stale" unless updates are propagated. + protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f); + + // this map is never stale + protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); + + // Generally updates to positions are made close to other updates, so we link to decrease cache misses when + // propagating updates + protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); + + @FunctionalInterface + public static interface LevelChangeCallback { + + /** + * This can be called for intermediate updates. So do not rely on newLevel being close to or + * the exact level that is expected after a full propagation has occured. + */ + public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); + + } + + protected final LevelChangeCallback changeCallback; + + public Delayed26WayDistancePropagator3D() { + this(null); + } + + public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) { + this.changeCallback = changeCallback; + } + + public int getLevel(final long pos) { + return this.levels.get(pos); + } + + public int getLevel(final int x, final int y, final int z) { + return this.levels.get(CoordinateUtils.getChunkSectionKey(x, y, z)); + } + + public void setSource(final int x, final int y, final int z, final int level) { + this.setSource(CoordinateUtils.getChunkSectionKey(x, y, z), level); + } + + public void setSource(final long coordinate, final int level) { + if ((level & 63) != level || level == 0) { + throw new IllegalArgumentException("Level must be in (0, 63], not " + level); + } + + final byte byteLevel = (byte)level; + final byte oldLevel = this.sources.put(coordinate, byteLevel); + + if (oldLevel == byteLevel) { + return; // nothing to do + } + + // queue to update later + this.updatedSources.add(coordinate); + } + + public void removeSource(final int x, final int y, final int z) { + this.removeSource(CoordinateUtils.getChunkSectionKey(x, y, z)); + } + + public void removeSource(final long coordinate) { + if (this.sources.remove(coordinate) != 0) { + this.updatedSources.add(coordinate); + } + } + + // queues used for BFS propagating levels + protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; + { + for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { + this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); + } + } + protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; + { + for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { + this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); + } + } + protected long levelIncreaseWorkQueueBitset; + protected long levelRemoveWorkQueueBitset; + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelIncreaseWorkQueueBitset |= (1L << level); + } + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelIncreaseWorkQueueBitset |= (1L << index); + } + + protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelRemoveWorkQueueBitset |= (1L << level); + } + + public boolean propagateUpdates() { + if (this.updatedSources.isEmpty()) { + return false; + } + + boolean ret = false; + + for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { + final long coordinate = iterator.nextLong(); + + final byte currentLevel = this.levels.get(coordinate); + final byte updatedSource = this.sources.get(coordinate); + + if (currentLevel == updatedSource) { + continue; + } + ret = true; + + if (updatedSource > currentLevel) { + // level increase + this.addToIncreaseWorkQueue(coordinate, updatedSource); + } else { + // level decrease + this.addToRemoveWorkQueue(coordinate, currentLevel); + // if the current coordinate is a source, then the decrease propagation will detect that and queue + // the source propagation + } + } + + this.updatedSources.clear(); + + // propagate source level increases first for performance reasons (in crowded areas hopefully the additions + // make the removes remove less) + this.propagateIncreases(); + + // now we propagate the decreases (which will then re-propagate clobbered sources) + this.propagateDecreases(); + + return ret; + } + + protected void propagateIncreases() { + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); + this.levelIncreaseWorkQueueBitset != 0L; + this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { + + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { + final long coordinate = queue.queuedCoordinates.removeFirstLong(); + byte level = queue.queuedLevels.removeFirstByte(); + + final boolean neighbourCheck = level < 0; + + final byte currentLevel; + if (neighbourCheck) { + level = (byte)-level; + currentLevel = this.levels.get(coordinate); + } else { + currentLevel = this.levels.putIfGreater(coordinate, level); + } + + if (neighbourCheck) { + // used when propagating from decrease to indicate that this level needs to check its neighbours + // this means the level at coordinate could be equal, but would still need neighbours checked + + if (currentLevel != level) { + // something caused the level to change, which means something propagated to it (which means + // us propagating here is redundant), or something removed the level (which means we + // cannot propagate further) + continue; + } + } else if (currentLevel >= level) { + // something higher/equal propagated + continue; + } + if (this.changeCallback != null) { + this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); + } + + if (level == 1) { + // can't propagate 0 to neighbours + continue; + } + + // propagate to neighbours + final byte neighbourLevel = (byte)(level - 1); + final int x = CoordinateUtils.getChunkSectionX(coordinate); + final int y = CoordinateUtils.getChunkSectionY(coordinate); + final int z = CoordinateUtils.getChunkSectionZ(coordinate); + + for (int dy = -1; dy <= 1; ++dy) { + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + if ((dy | dz | dx) == 0) { + // already propagated to coordinate + continue; + } + + // sure we can check the neighbour level in the map right now and avoid a propagation, + // but then we would still have to recheck it when popping the value off of the queue! + // so just avoid the double lookup + final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); + this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); + } + } + } + } + } + } + + protected void propagateDecreases() { + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); + this.levelRemoveWorkQueueBitset != 0L; + this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { + + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { + final long coordinate = queue.queuedCoordinates.removeFirstLong(); + final byte level = queue.queuedLevels.removeFirstByte(); + + final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); + if (currentLevel == 0) { + // something else removed + continue; + } + + if (currentLevel > level) { + // something higher propagated here or we hit the propagation of another source + // in the second case we need to re-propagate because we could have just clobbered another source's + // propagation + this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking + continue; + } + + if (this.changeCallback != null) { + this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); + } + + final byte source = this.sources.get(coordinate); + if (source != 0) { + // must re-propagate source later + this.addToIncreaseWorkQueue(coordinate, source); + } + + if (level == 0) { + // can't propagate -1 to neighbours + // we have to check neighbours for removing 1 just in case the neighbour is 2 + continue; + } + + // propagate to neighbours + final byte neighbourLevel = (byte)(level - 1); + final int x = CoordinateUtils.getChunkSectionX(coordinate); + final int y = CoordinateUtils.getChunkSectionY(coordinate); + final int z = CoordinateUtils.getChunkSectionZ(coordinate); + + for (int dy = -1; dy <= 1; ++dy) { + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + if ((dy | dz | dx) == 0) { + // already propagated to coordinate + continue; + } + + // sure we can check the neighbour level in the map right now and avoid a propagation, + // but then we would still have to recheck it when popping the value off of the queue! + // so just avoid the double lookup + final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); + this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); + } + } + } + } + } + + // propagate sources we clobbered in the process + this.propagateIncreases(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java new file mode 100644 index 0000000000..ab2fa1563d --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java @@ -0,0 +1,718 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; + +public final class Delayed8WayDistancePropagator2D { + + // Test + /* + protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference, Delayed8WayDistancePropagator2D test) { + int got = test.getLevel(x, z); + + int expect = 0; + Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet(); + if (nearest != null) { + for (Object _obj : nearest) { + if (_obj instanceof Ticket) { + Ticket ticket = (Ticket)_obj; + long ticketCoord = reference.getLastCoordinate(ticket); + int viewDistance = reference.getLastViewDistance(ticket); + int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x), + com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z)); + int level = viewDistance - distance; + if (level > expect) { + expect = level; + } + } + } + } + + if (expect != got) { + throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got); + } + } + + static class Ticket { + + int x; + int z; + + final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty + = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); + + } + + public static void main(final String[] args) { + com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap() { + @Override + protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(Ticket object) { + return object.empty; + } + }; + Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D(); + + final int maxDistance = 64; + // test origin + { + Ticket originTicket = new Ticket(); + int originDistance = 31; + // test single source + reference.add(originTicket, 0, 0, originDistance); + test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate + for (int dx = -originDistance; dx <= originDistance; ++dx) { + for (int dz = -originDistance; dz <= originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + // test single source decrease + reference.update(originTicket, 0, 0, originDistance/2); + test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate + for (int dx = -originDistance; dx <= originDistance; ++dx) { + for (int dz = -originDistance; dz <= originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + // test source increase + originDistance = 2*originDistance; + reference.update(originTicket, 0, 0, originDistance); + test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate + for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { + for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + reference.remove(originTicket); + test.removeSource(0, 0); test.propagateUpdates(); + } + + // test multiple sources at origin + { + int originDistance = 31; + java.util.List list = new java.util.ArrayList<>(); + for (int i = 0; i < 10; ++i) { + Ticket a = new Ticket(); + list.add(a); + a.x = (i & 1) == 1 ? -i : i; + a.z = (i & 1) == 1 ? -i : i; + } + for (Ticket ticket : list) { + reference.add(ticket, ticket.x, ticket.z, originDistance); + test.setSource(ticket.x, ticket.z, originDistance); + } + test.propagateUpdates(); + + for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { + for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket level decrease + + for (Ticket ticket : list) { + reference.update(ticket, ticket.x, ticket.z, originDistance/2); + test.setSource(ticket.x, ticket.z, originDistance/2); + } + test.propagateUpdates(); + + for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { + for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket level increase + + for (Ticket ticket : list) { + reference.update(ticket, ticket.x, ticket.z, originDistance*2); + test.setSource(ticket.x, ticket.z, originDistance*2); + } + test.propagateUpdates(); + + for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { + for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket remove + for (int i = 0, len = list.size(); i < len; ++i) { + if ((i & 3) != 0) { + continue; + } + Ticket ticket = list.get(i); + reference.remove(ticket); + test.removeSource(ticket.x, ticket.z); + } + test.propagateUpdates(); + + for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { + for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + } + + // now test at coordinate offsets + // test offset + { + Ticket originTicket = new Ticket(); + int originDistance = 31; + int offX = 54432; + int offZ = -134567; + // test single source + reference.add(originTicket, offX, offZ, originDistance); + test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate + for (int dx = -originDistance; dx <= originDistance; ++dx) { + for (int dz = -originDistance; dz <= originDistance; ++dz) { + test(dx + offX, dz + offZ, reference, test); + } + } + // test single source decrease + reference.update(originTicket, offX, offZ, originDistance/2); + test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate + for (int dx = -originDistance; dx <= originDistance; ++dx) { + for (int dz = -originDistance; dz <= originDistance; ++dz) { + test(dx + offX, dz + offZ, reference, test); + } + } + // test source increase + originDistance = 2*originDistance; + reference.update(originTicket, offX, offZ, originDistance); + test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate + for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { + for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { + test(dx + offX, dz + offZ, reference, test); + } + } + + reference.remove(originTicket); + test.removeSource(offX, offZ); test.propagateUpdates(); + } + + // test multiple sources at origin + { + int originDistance = 31; + int offX = 54432; + int offZ = -134567; + java.util.List list = new java.util.ArrayList<>(); + for (int i = 0; i < 10; ++i) { + Ticket a = new Ticket(); + list.add(a); + a.x = offX + ((i & 1) == 1 ? -i : i); + a.z = offZ + ((i & 1) == 1 ? -i : i); + } + for (Ticket ticket : list) { + reference.add(ticket, ticket.x, ticket.z, originDistance); + test.setSource(ticket.x, ticket.z, originDistance); + } + test.propagateUpdates(); + + for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { + for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket level decrease + + for (Ticket ticket : list) { + reference.update(ticket, ticket.x, ticket.z, originDistance/2); + test.setSource(ticket.x, ticket.z, originDistance/2); + } + test.propagateUpdates(); + + for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { + for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket level increase + + for (Ticket ticket : list) { + reference.update(ticket, ticket.x, ticket.z, originDistance*2); + test.setSource(ticket.x, ticket.z, originDistance*2); + } + test.propagateUpdates(); + + for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { + for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + + // test ticket remove + for (int i = 0, len = list.size(); i < len; ++i) { + if ((i & 3) != 0) { + continue; + } + Ticket ticket = list.get(i); + reference.remove(ticket); + test.removeSource(ticket.x, ticket.z); + } + test.propagateUpdates(); + + for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { + for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { + test(dx, dz, reference, test); + } + } + } + } + */ + + // this map is considered "stale" unless updates are propagated. + protected final LevelMap levels = new LevelMap(8192*2, 0.6f); + + // this map is never stale + protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); + + // Generally updates to positions are made close to other updates, so we link to decrease cache misses when + // propagating updates + protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); + + @FunctionalInterface + public static interface LevelChangeCallback { + + /** + * This can be called for intermediate updates. So do not rely on newLevel being close to or + * the exact level that is expected after a full propagation has occured. + */ + public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); + + } + + protected final LevelChangeCallback changeCallback; + + public Delayed8WayDistancePropagator2D() { + this(null); + } + + public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) { + this.changeCallback = changeCallback; + } + + public int getLevel(final long pos) { + return this.levels.get(pos); + } + + public int getLevel(final int x, final int z) { + return this.levels.get(CoordinateUtils.getChunkKey(x, z)); + } + + public void setSource(final int x, final int z, final int level) { + this.setSource(CoordinateUtils.getChunkKey(x, z), level); + } + + public void setSource(final long coordinate, final int level) { + if ((level & 63) != level || level == 0) { + throw new IllegalArgumentException("Level must be in (0, 63], not " + level); + } + + final byte byteLevel = (byte)level; + final byte oldLevel = this.sources.put(coordinate, byteLevel); + + if (oldLevel == byteLevel) { + return; // nothing to do + } + + // queue to update later + this.updatedSources.add(coordinate); + } + + public void removeSource(final int x, final int z) { + this.removeSource(CoordinateUtils.getChunkKey(x, z)); + } + + public void removeSource(final long coordinate) { + if (this.sources.remove(coordinate) != 0) { + this.updatedSources.add(coordinate); + } + } + + // queues used for BFS propagating levels + protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64]; + { + for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { + this.levelIncreaseWorkQueues[i] = new WorkQueue(); + } + } + protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64]; + { + for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { + this.levelRemoveWorkQueues[i] = new WorkQueue(); + } + } + protected long levelIncreaseWorkQueueBitset; + protected long levelRemoveWorkQueueBitset; + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { + final WorkQueue queue = this.levelIncreaseWorkQueues[level]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelIncreaseWorkQueueBitset |= (1L << level); + } + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { + final WorkQueue queue = this.levelIncreaseWorkQueues[index]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelIncreaseWorkQueueBitset |= (1L << index); + } + + protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { + final WorkQueue queue = this.levelRemoveWorkQueues[level]; + queue.queuedCoordinates.enqueue(coordinate); + queue.queuedLevels.enqueue(level); + + this.levelRemoveWorkQueueBitset |= (1L << level); + } + + public boolean propagateUpdates() { + if (this.updatedSources.isEmpty()) { + return false; + } + + boolean ret = false; + + for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { + final long coordinate = iterator.nextLong(); + + final byte currentLevel = this.levels.get(coordinate); + final byte updatedSource = this.sources.get(coordinate); + + if (currentLevel == updatedSource) { + continue; + } + ret = true; + + if (updatedSource > currentLevel) { + // level increase + this.addToIncreaseWorkQueue(coordinate, updatedSource); + } else { + // level decrease + this.addToRemoveWorkQueue(coordinate, currentLevel); + // if the current coordinate is a source, then the decrease propagation will detect that and queue + // the source propagation + } + } + + this.updatedSources.clear(); + + // propagate source level increases first for performance reasons (in crowded areas hopefully the additions + // make the removes remove less) + this.propagateIncreases(); + + // now we propagate the decreases (which will then re-propagate clobbered sources) + this.propagateDecreases(); + + return ret; + } + + protected void propagateIncreases() { + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); + this.levelIncreaseWorkQueueBitset != 0L; + this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { + + final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { + final long coordinate = queue.queuedCoordinates.removeFirstLong(); + byte level = queue.queuedLevels.removeFirstByte(); + + final boolean neighbourCheck = level < 0; + + final byte currentLevel; + if (neighbourCheck) { + level = (byte)-level; + currentLevel = this.levels.get(coordinate); + } else { + currentLevel = this.levels.putIfGreater(coordinate, level); + } + + if (neighbourCheck) { + // used when propagating from decrease to indicate that this level needs to check its neighbours + // this means the level at coordinate could be equal, but would still need neighbours checked + + if (currentLevel != level) { + // something caused the level to change, which means something propagated to it (which means + // us propagating here is redundant), or something removed the level (which means we + // cannot propagate further) + continue; + } + } else if (currentLevel >= level) { + // something higher/equal propagated + continue; + } + if (this.changeCallback != null) { + this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); + } + + if (level == 1) { + // can't propagate 0 to neighbours + continue; + } + + // propagate to neighbours + final byte neighbourLevel = (byte)(level - 1); + final int x = (int)coordinate; + final int z = (int)(coordinate >>> 32); + + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + if ((dx | dz) == 0) { + // already propagated to coordinate + continue; + } + + // sure we can check the neighbour level in the map right now and avoid a propagation, + // but then we would still have to recheck it when popping the value off of the queue! + // so just avoid the double lookup + final long neighbourCoordinate = CoordinateUtils.getChunkKey(x + dx, z + dz); + this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); + } + } + } + } + } + + protected void propagateDecreases() { + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); + this.levelRemoveWorkQueueBitset != 0L; + this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { + + final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { + final long coordinate = queue.queuedCoordinates.removeFirstLong(); + final byte level = queue.queuedLevels.removeFirstByte(); + + final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); + if (currentLevel == 0) { + // something else removed + continue; + } + + if (currentLevel > level) { + // something higher propagated here or we hit the propagation of another source + // in the second case we need to re-propagate because we could have just clobbered another source's + // propagation + this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking + continue; + } + + if (this.changeCallback != null) { + this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); + } + + final byte source = this.sources.get(coordinate); + if (source != 0) { + // must re-propagate source later + this.addToIncreaseWorkQueue(coordinate, source); + } + + if (level == 0) { + // can't propagate -1 to neighbours + // we have to check neighbours for removing 1 just in case the neighbour is 2 + continue; + } + + // propagate to neighbours + final byte neighbourLevel = (byte)(level - 1); + final int x = (int)coordinate; + final int z = (int)(coordinate >>> 32); + + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + if ((dx | dz) == 0) { + // already propagated to coordinate + continue; + } + + // sure we can check the neighbour level in the map right now and avoid a propagation, + // but then we would still have to recheck it when popping the value off of the queue! + // so just avoid the double lookup + final long neighbourCoordinate = CoordinateUtils.getChunkKey(x + dx, z + dz); + this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); + } + } + } + } + + // propagate sources we clobbered in the process + this.propagateIncreases(); + } + + protected static final class LevelMap extends Long2ByteOpenHashMap { + public LevelMap() { + super(); + } + + public LevelMap(final int expected, final float loadFactor) { + super(expected, loadFactor); + } + + // copied from superclass + private int find(final long k) { + if (k == 0L) { + return this.containsNullKey ? this.n : -(this.n + 1); + } else { + final long[] key = this.key; + long curr; + int pos; + if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) { + return -(pos + 1); + } else if (k == curr) { + return pos; + } else { + while((curr = key[pos = pos + 1 & this.mask]) != 0L) { + if (k == curr) { + return pos; + } + } + + return -(pos + 1); + } + } + } + + // copied from superclass + private void insert(final int pos, final long k, final byte v) { + if (pos == this.n) { + this.containsNullKey = true; + } + + this.key[pos] = k; + this.value[pos] = v; + if (this.size++ >= this.maxFill) { + this.rehash(HashCommon.arraySize(this.size + 1, this.f)); + } + } + + // copied from superclass + public byte putIfGreater(final long key, final byte value) { + final int pos = this.find(key); + if (pos < 0) { + if (this.defRetValue < value) { + this.insert(-pos - 1, key, value); + } + return this.defRetValue; + } else { + final byte curr = this.value[pos]; + if (value > curr) { + this.value[pos] = value; + return curr; + } + return curr; + } + } + + // copied from superclass + private void removeEntry(final int pos) { + --this.size; + this.shiftKeys(pos); + if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { + this.rehash(this.n / 2); + } + } + + // copied from superclass + private void removeNullEntry() { + this.containsNullKey = false; + --this.size; + if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { + this.rehash(this.n / 2); + } + } + + // copied from superclass + public byte removeIfGreaterOrEqual(final long key, final byte value) { + if (key == 0L) { + if (!this.containsNullKey) { + return this.defRetValue; + } + final byte current = this.value[this.n]; + if (value >= current) { + this.removeNullEntry(); + return current; + } + return current; + } else { + long[] keys = this.key; + byte[] values = this.value; + long curr; + int pos; + if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) { + return this.defRetValue; + } else if (key == curr) { + final byte current = values[pos]; + if (value >= current) { + this.removeEntry(pos); + return current; + } + return current; + } else { + while((curr = keys[pos = pos + 1 & this.mask]) != 0L) { + if (key == curr) { + final byte current = values[pos]; + if (value >= current) { + this.removeEntry(pos); + return current; + } + return current; + } + } + + return this.defRetValue; + } + } + } + } + + protected static final class WorkQueue { + + public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); + public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); + + } + + protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { + + /** + * Assumes non-empty. If empty, undefined behaviour. + */ + public long removeFirstLong() { + // copied from superclass + long t = this.array[this.start]; + if (++this.start == this.length) { + this.start = 0; + } + + return t; + } + } + + protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { + + /** + * Assumes non-empty. If empty, undefined behaviour. + */ + public byte removeFirstByte() { + // copied from superclass + byte t = this.array[this.start]; + if (++this.start == this.length) { + this.start = 0; + } + + return t; + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java new file mode 100644 index 0000000000..c2d917c2ea --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java @@ -0,0 +1,22 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import java.lang.invoke.VarHandle; + +public final class LazyRunnable implements Runnable { + + private volatile Runnable toRun; + private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); + + public void setRunnable(final Runnable run) { + final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); + if (prev != null) { + throw new IllegalStateException("Runnable already set"); + } + } + + @Override + public void run() { + ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java new file mode 100644 index 0000000000..90560769d0 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java @@ -0,0 +1,99 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.concurrentutil.util.IntPairUtil; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceSet; + +public final class PositionCountingAreaMap { + + private final Reference2ReferenceOpenHashMap counters = new Reference2ReferenceOpenHashMap<>(); + private final Long2IntOpenHashMap positions = new Long2IntOpenHashMap(); + + public ReferenceSet getObjects() { + return this.counters.keySet(); + } + + public LongSet getPositions() { + return this.positions.keySet(); + } + + public int getTotalPositions() { + return this.positions.size(); + } + + public boolean hasObjectsNear(final int toX, final int toZ) { + return this.positions.containsKey(IntPairUtil.key(toX, toZ)); + } + + public int getObjectsNear(final int toX, final int toZ) { + return this.positions.get(IntPairUtil.key(toX, toZ)); + } + + public boolean add(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter existing = this.counters.get(parameter); + if (existing != null) { + return false; + } + + final PositionCounter counter = new PositionCounter(parameter); + + this.counters.put(parameter, counter); + + return counter.add(toX, toZ, distance); + } + + public boolean addOrUpdate(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter existing = this.counters.get(parameter); + if (existing != null) { + return existing.update(toX, toZ, distance); + } + + final PositionCounter counter = new PositionCounter(parameter); + + this.counters.put(parameter, counter); + + return counter.add(toX, toZ, distance); + } + + public boolean remove(final T parameter) { + final PositionCounter counter = this.counters.remove(parameter); + if (counter == null) { + return false; + } + + counter.remove(); + + return true; + } + + public boolean update(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter counter = this.counters.get(parameter); + if (counter == null) { + return false; + } + + return counter.update(toX, toZ, distance); + } + + private final class PositionCounter extends SingleUserAreaMap { + + public PositionCounter(final T parameter) { + super(parameter); + } + + @Override + protected void addCallback(final T parameter, final int toX, final int toZ) { + PositionCountingAreaMap.this.positions.addTo(IntPairUtil.key(toX, toZ), 1); + } + + @Override + protected void removeCallback(final T parameter, final int toX, final int toZ) { + final long key = IntPairUtil.key(toX, toZ); + if (PositionCountingAreaMap.this.positions.addTo(key, -1) == 1) { + PositionCountingAreaMap.this.positions.remove(key); + } + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java new file mode 100644 index 0000000000..94689e0342 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java @@ -0,0 +1,248 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.concurrentutil.util.IntegerUtil; + +public abstract class SingleUserAreaMap { + + public static final int NOT_SET = Integer.MIN_VALUE; + + private final T parameter; + private int lastChunkX = NOT_SET; + private int lastChunkZ = NOT_SET; + private int distance = NOT_SET; + + public SingleUserAreaMap(final T parameter) { + this.parameter = parameter; + } + + public final T getParameter() { + return this.parameter; + } + + public final int getLastChunkX() { + return this.lastChunkX; + } + + public final int getLastChunkZ() { + return this.lastChunkZ; + } + + public final int getLastDistance() { + return this.distance; + } + + /* math sign function except 0 returns 1 */ + protected static int sign(int val) { + return 1 | (val >> (Integer.SIZE - 1)); + } + + protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ); + + protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ); + + private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) { + final int maxX = chunkX + distance; + final int maxZ = chunkZ + distance; + + for (int cx = chunkX - distance; cx <= maxX; ++cx) { + for (int cz = chunkZ - distance; cz <= maxZ; ++cz) { + this.addCallback(parameter, cx, cz); + } + } + } + + private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) { + final int maxX = chunkX + distance; + final int maxZ = chunkZ + distance; + + for (int cx = chunkX - distance; cx <= maxX; ++cx) { + for (int cz = chunkZ - distance; cz <= maxZ; ++cz) { + this.removeCallback(parameter, cx, cz); + } + } + } + + public final boolean add(final int chunkX, final int chunkZ, final int distance) { + if (distance < 0) { + throw new IllegalArgumentException(Integer.toString(distance)); + } + if (this.lastChunkX != NOT_SET) { + return false; + } + this.lastChunkX = chunkX; + this.lastChunkZ = chunkZ; + this.distance = distance; + + this.addToNew(this.parameter, chunkX, chunkZ, distance); + + return true; + } + + public final boolean update(final int toX, final int toZ, final int newViewDistance) { + if (newViewDistance < 0) { + throw new IllegalArgumentException(Integer.toString(newViewDistance)); + } + final int fromX = this.lastChunkX; + final int fromZ = this.lastChunkZ; + final int oldViewDistance = this.distance; + if (fromX == NOT_SET) { + return false; + } + + this.lastChunkX = toX; + this.lastChunkZ = toZ; + this.distance = newViewDistance; + + final T parameter = this.parameter; + + + final int dx = toX - fromX; + final int dz = toZ - fromZ; + + final int totalX = IntegerUtil.branchlessAbs(fromX - toX); + final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); + + if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { + // teleported + this.removeFromOld(parameter, fromX, fromZ, oldViewDistance); + this.addToNew(parameter, toX, toZ, newViewDistance); + return true; + } + + if (oldViewDistance != newViewDistance) { + // remove loop + + final int oldMinX = fromX - oldViewDistance; + final int oldMinZ = fromZ - oldViewDistance; + final int oldMaxX = fromX + oldViewDistance; + final int oldMaxZ = fromZ + oldViewDistance; + for (int currX = oldMinX; currX <= oldMaxX; ++currX) { + for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) { + + // only remove if we're outside the new view distance... + if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) { + this.removeCallback(parameter, currX, currZ); + } + } + } + + // add loop + + final int newMinX = toX - newViewDistance; + final int newMinZ = toZ - newViewDistance; + final int newMaxX = toX + newViewDistance; + final int newMaxZ = toZ + newViewDistance; + for (int currX = newMinX; currX <= newMaxX; ++currX) { + for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) { + + // only add if we're outside the old view distance... + if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) { + this.addCallback(parameter, currX, currZ); + } + } + } + + return true; + } + + // x axis is width + // z axis is height + // right refers to the x axis of where we moved + // top refers to the z axis of where we moved + + // same view distance + + // used for relative positioning + final int up = sign(dz); // 1 if dz >= 0, -1 otherwise + final int right = sign(dx); // 1 if dx >= 0, -1 otherwise + + // The area excluded by overlapping the two view distance squares creates four rectangles: + // Two on the left, and two on the right. The ones on the left we consider the "removed" section + // and on the right the "added" section. + // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually + // exclusive to the regions they surround. + + // 4 points of the rectangle + int maxX; // exclusive + int minX; // inclusive + int maxZ; // exclusive + int minZ; // inclusive + + if (dx != 0) { + // handle right addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = fromX + (oldViewDistance * right) + right; // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addCallback(parameter, currX, currZ); + } + } + } + + if (dz != 0) { + // handle up addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = toX - (oldViewDistance * right); // inclusive + maxZ = toZ + (oldViewDistance * up) + up; // exclusive + minZ = fromZ + (oldViewDistance * up) + up; // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addCallback(parameter, currX, currZ); + } + } + } + + if (dx != 0) { + // handle left removal + + maxX = toX - (oldViewDistance * right); // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeCallback(parameter, currX, currZ); + } + } + } + + if (dz != 0) { + // handle down removal + + maxX = fromX + (oldViewDistance * right) + right; // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = toZ - (oldViewDistance * up); // exclusive + minZ = fromZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeCallback(parameter, currX, currZ); + } + } + } + + return true; + } + + public final boolean remove() { + final int chunkX = this.lastChunkX; + final int chunkZ = this.lastChunkZ; + final int distance = this.distance; + if (chunkX == NOT_SET) { + return false; + } + + this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET; + + this.removeFromOld(this.parameter, chunkX, chunkZ, distance); + + return true; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java new file mode 100644 index 0000000000..4123edddc5 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java @@ -0,0 +1,68 @@ +package ca.spottedleaf.moonrise.common.set; + +import java.util.Collection; + +public final class OptimizedSmallEnumSet> { + + private final Class enumClass; + private long backingSet; + + public OptimizedSmallEnumSet(final Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("Null class"); + } + if (!clazz.isEnum()) { + throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); + } + this.enumClass = clazz; + } + + public boolean addUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev | key; + + return (prev & key) == 0; + } + + public boolean removeUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev & ~key; + + return (prev & key) != 0; + } + + public void clear() { + this.backingSet = 0L; + } + + public int size() { + return Long.bitCount(this.backingSet); + } + + public void addAllUnchecked(final Collection enums) { + for (final E element : enums) { + if (element == null) { + throw new NullPointerException("Null element"); + } + this.backingSet |= (1L << element.ordinal()); + } + } + + public long getBackingSet() { + return this.backingSet; + } + + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } + + public boolean hasElement(final E element) { + return (this.backingSet & (1L << element.ordinal())) != 0; + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java new file mode 100644 index 0000000000..58a99bc38e --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java @@ -0,0 +1,288 @@ +package ca.spottedleaf.moonrise.common.util; + +import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.PlatformHooks; +import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.slf4j.Logger; +import java.util.List; +import java.util.function.Consumer; + +public final class ChunkSystem { + + private static final Logger LOGGER = LogUtils.getLogger(); + private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL); + + private static int getDistance(final ChunkStatus status) { + return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status); + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { + scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { + level.chunkSource.mainThreadProcessor.execute(run); + } + + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, + final ChunkStatus toStatus, final boolean addTicket, final Priority priority, + final Consumer onComplete) { + if (gen) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + return; + } + scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { + if (chunk == null) { + if (onComplete != null) { + onComplete.accept(null); + } + } else { + if (chunk.getPersistedStatus().isOrAfter(toStatus)) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + } else { + if (onComplete != null) { + onComplete.accept(null); + } + } + } + }); + } + + static final net.minecraft.server.level.TicketType CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo); + + private static long chunkLoadCounter = 0L; + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, + final boolean addTicket, final Priority priority, final Consumer onComplete) { + if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) { + scheduleChunkTask(level, chunkX, chunkZ, () -> { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + }, priority); + return; + } + + final int minLevel = 33 + getDistance(toStatus); + final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; + final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ); + + if (addTicket) { + level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + level.chunkSource.runDistanceManagerUpdates(); + + final Consumer loadCallback = (final ChunkAccess chunk) -> { + try { + if (onComplete != null) { + onComplete.accept(chunk); + } + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + com.destroystokyo.paper.util.SneakyThrow.sneaky(thr); + } finally { + if (addTicket) { + level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); + level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + } + }; + + final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (holder == null || holder.getTicketLevel() > minLevel) { + loadCallback.accept(null); + return; + } + + final java.util.concurrent.CompletableFuture> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap); + + if (loadFuture.isDone()) { + loadCallback.accept(loadFuture.join().orElse(null)); + return; + } + + loadFuture.whenCompleteAsync((final net.minecraft.server.level.ChunkResult result, final Throwable thr) -> { + if (thr != null) { + loadCallback.accept(null); + return; + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { + scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, + final Priority priority, final Consumer onComplete) { + // This method goes unused until the chunk system rewrite + if (toStatus == FullChunkStatus.INACCESSIBLE) { + throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); + } + + if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) { + scheduleChunkTask(level, chunkX, chunkZ, () -> { + scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + }, priority); + return; + } + + final int minLevel = 33 - (toStatus.ordinal() - 1); + final int radius = toStatus.ordinal() - 1; + final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; + final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ); + + if (addTicket) { + level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + level.chunkSource.runDistanceManagerUpdates(); + + final Consumer loadCallback = (final LevelChunk chunk) -> { + try { + if (onComplete != null) { + onComplete.accept(chunk); + } + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + com.destroystokyo.paper.util.SneakyThrow.sneaky(thr); + } finally { + if (addTicket) { + level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); + level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + } + }; + + final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (holder == null || holder.getTicketLevel() > minLevel) { + loadCallback.accept(null); + return; + } + + final java.util.concurrent.CompletableFuture> tickingState; + switch (toStatus) { + case FULL: { + tickingState = holder.getFullChunkFuture(); + break; + } + case BLOCK_TICKING: { + tickingState = holder.getTickingChunkFuture(); + break; + } + case ENTITY_TICKING: { + tickingState = holder.getEntityTickingChunkFuture(); + break; + } + default: { + throw new IllegalStateException("Cannot reach here"); + } + } + + if (tickingState.isDone()) { + loadCallback.accept(tickingState.join().orElse(null)); + return; + } + + tickingState.whenCompleteAsync((final net.minecraft.server.level.ChunkResult result, final Throwable thr) -> { + if (thr != null) { + loadCallback.accept(null); + return; + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { + scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + public static List getVisibleChunkHolders(final ServerLevel level) { + return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values()); + } + + public static List getUpdatingChunkHolders(final ServerLevel level) { + return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values()); + } + + public static int getVisibleChunkHolderCount(final ServerLevel level) { + return level.chunkSource.chunkMap.visibleChunkMap.size(); + } + + public static int getUpdatingChunkHolderCount(final ServerLevel level) { + return level.chunkSource.chunkMap.updatingChunkMap.size(); + } + + public static boolean hasAnyChunkHolders(final ServerLevel level) { + return getUpdatingChunkHolderCount(level) != 0; + } + + public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { + if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { + return false; + } + return true; + } + + public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { + + } + + public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { + + } + + public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { + return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ); + } + + public static int getSendViewDistance(final ServerPlayer player) { + return getViewDistance(player); + } + + public static int getViewDistance(final ServerPlayer player) { + final ServerLevel level = player.serverLevel(); + if (level == null) { + return org.bukkit.Bukkit.getViewDistance(); + } + return level.chunkSource.chunkMap.serverViewDistance; + } + + public static int getTickViewDistance(final ServerPlayer player) { + final ServerLevel level = player.serverLevel(); + if (level == null) { + return org.bukkit.Bukkit.getSimulationDistance(); + } + return level.chunkSource.chunkMap.distanceManager.simulationDistance; + } + + private ChunkSystem() {} +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java new file mode 100644 index 0000000000..31b92bd488 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java @@ -0,0 +1,129 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.phys.Vec3; + +public final class CoordinateUtils { + + // the chunk keys are compatible with vanilla + + public static long getChunkKey(final BlockPos pos) { + return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); + } + + public static long getChunkKey(final Entity entity) { + return ((Mth.lfloor(entity.getZ()) >> 4) << 32) | ((Mth.lfloor(entity.getX()) >> 4) & 0xFFFFFFFFL); + } + + public static long getChunkKey(final ChunkPos pos) { + return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); + } + + public static long getChunkKey(final SectionPos pos) { + return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); + } + + public static long getChunkKey(final int x, final int z) { + return ((long)z << 32) | (x & 0xFFFFFFFFL); + } + + public static int getChunkX(final long chunkKey) { + return (int)chunkKey; + } + + public static int getChunkZ(final long chunkKey) { + return (int)(chunkKey >>> 32); + } + + public static int getChunkCoordinate(final double blockCoordinate) { + return Mth.floor(blockCoordinate) >> 4; + } + + // the section keys are compatible with vanilla's + + static final int SECTION_X_BITS = 22; + static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; + static final int SECTION_Y_BITS = 20; + static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; + static final int SECTION_Z_BITS = 22; + static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; + // format is y,z,x (in order of LSB to MSB) + static final int SECTION_Y_SHIFT = 0; + static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; + static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; + static final int SECTION_TO_BLOCK_SHIFT = 4; + + public static long getChunkSectionKey(final int x, final int y, final int z) { + return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) + | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) + | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); + } + + public static long getChunkSectionKey(final SectionPos pos) { + return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) + | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) + | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); + } + + public static long getChunkSectionKey(final ChunkPos pos, final int y) { + return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) + | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) + | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); + } + + public static long getChunkSectionKey(final BlockPos pos) { + return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | + ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | + (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); + } + + public static long getChunkSectionKey(final Entity entity) { + return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | + ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | + ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); + } + + public static int getChunkSectionX(final long key) { + return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); + } + + public static int getChunkSectionY(final long key) { + return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); + } + + public static int getChunkSectionZ(final long key) { + return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); + } + + public static int getBlockX(final Vec3 pos) { + return Mth.floor(pos.x); + } + + public static int getBlockY(final Vec3 pos) { + return Mth.floor(pos.y); + } + + public static int getBlockZ(final Vec3 pos) { + return Mth.floor(pos.z); + } + + public static int getChunkX(final Vec3 pos) { + return Mth.floor(pos.x) >> 4; + } + + public static int getChunkY(final Vec3 pos) { + return Mth.floor(pos.y) >> 4; + } + + public static int getChunkZ(final Vec3 pos) { + return Mth.floor(pos.z) >> 4; + } + + private CoordinateUtils() { + throw new RuntimeException(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java new file mode 100644 index 0000000000..0531f25aaa --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java @@ -0,0 +1,109 @@ +package ca.spottedleaf.moonrise.common.util; + +import java.util.Objects; + +public final class FlatBitsetUtil { + + private static final int LOG2_LONG = 6; + private static final long ALL_SET = -1L; + private static final int BITS_PER_LONG = Long.SIZE; + + // from inclusive + // to exclusive + public static int firstSet(final long[] bitset, final int from, final int to) { + if ((from | to | (to - from)) < 0) { + throw new IndexOutOfBoundsException(); + } + + int bitsetIdx = from >>> LOG2_LONG; + int bitIdx = from & ~(BITS_PER_LONG - 1); + + long tmp = bitset[bitsetIdx] & (ALL_SET << from); + for (;;) { + if (tmp != 0L) { + final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); + return ret >= to ? -1 : ret; + } + + bitIdx += BITS_PER_LONG; + + if (bitIdx >= to) { + return -1; + } + + tmp = bitset[++bitsetIdx]; + } + } + + // from inclusive + // to exclusive + public static int firstClear(final long[] bitset, final int from, final int to) { + if ((from | to | (to - from)) < 0) { + throw new IndexOutOfBoundsException(); + } + // like firstSet, but invert the bitset + + int bitsetIdx = from >>> LOG2_LONG; + int bitIdx = from & ~(BITS_PER_LONG - 1); + + long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from); + for (;;) { + if (tmp != 0L) { + final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); + return ret >= to ? -1 : ret; + } + + bitIdx += BITS_PER_LONG; + + if (bitIdx >= to) { + return -1; + } + + tmp = ~bitset[++bitsetIdx]; + } + } + + // from inclusive + // to exclusive + public static void clearRange(final long[] bitset, final int from, int to) { + if ((from | to | (to - from)) < 0) { + throw new IndexOutOfBoundsException(); + } + + if (from == to) { + return; + } + + --to; + + final int fromBitsetIdx = from >>> LOG2_LONG; + final int toBitsetIdx = to >>> LOG2_LONG; + + final long keepFirst = ~(ALL_SET << from); + final long keepLast = ~(ALL_SET >>> ((BITS_PER_LONG - 1) ^ to)); + + Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length); + + if (fromBitsetIdx == toBitsetIdx) { + // special case: need to keep both first and last + bitset[fromBitsetIdx] &= (keepFirst | keepLast); + } else { + bitset[fromBitsetIdx] &= keepFirst; + + for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) { + bitset[i] = 0L; + } + + bitset[toBitsetIdx] &= keepLast; + } + } + + // from inclusive + // to exclusive + public static boolean isRangeSet(final long[] bitset, final int from, final int to) { + return firstClear(bitset, from, to) == -1; + } + + + private FlatBitsetUtil() {} +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java new file mode 100644 index 0000000000..91efda726b --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java @@ -0,0 +1,34 @@ +package ca.spottedleaf.moonrise.common.util; + +import com.google.gson.JsonElement; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +public final class JsonUtil { + + public static void writeJson(final JsonElement element, final File file) throws IOException { + final StringWriter stringWriter = new StringWriter(); + final JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setIndent(" "); + jsonWriter.setLenient(false); + Streams.write(element, jsonWriter); + + final String jsonString = stringWriter.toString(); + + final File parent = file.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + file.createNewFile(); + try (final PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { + out.print(jsonString); + } + } + +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java new file mode 100644 index 0000000000..97848869df --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java @@ -0,0 +1,14 @@ +package ca.spottedleaf.moonrise.common.util; + +public final class MixinWorkarounds { + + // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs + // https://github.com/FabricMC/Mixin/pull/147 + public static long[] clone(final long[] values) { + return values.clone(); + } + + public static byte[] clone(final byte[] values) { + return values.clone(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java new file mode 100644 index 0000000000..632920e046 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java @@ -0,0 +1,101 @@ +package ca.spottedleaf.moonrise.common.util; + +import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; +import ca.spottedleaf.moonrise.common.PlatformHooks; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +public final class MoonriseCommon { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + + public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( + new Consumer<>() { + private final AtomicInteger idGenerator = new AtomicInteger(); + + @Override + public void accept(Thread thread) { + thread.setDaemon(true); + thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); + } + }); + } + } + ); + public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms + public static final int CLIENT_DIVISION = 0; + public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); + public static final int SERVER_DIVISION = 1; + public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); + public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); + public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); + + public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } + defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + + int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + + final int ioThreads = Math.max(1, configIoThreads); + + WORKER_POOL.adjustThreadCount(workerThreads); + IO_POOL.adjustThreadCount(ioThreads); + + LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); + } + + public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( + new Consumer<>() { + private final AtomicInteger idGenerator = new AtomicInteger(); + + @Override + public void accept(final Thread thread) { + thread.setDaemon(true); + thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); + } + }); + } + } + ); + public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms + public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); + public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); + + public static void haltExecutors() { + MoonriseCommon.WORKER_POOL.shutdown(false); + LOGGER.info("Awaiting termination of worker pool for up to 60s..."); + if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { + LOGGER.error("Worker pool did not shut down in time!"); + MoonriseCommon.WORKER_POOL.halt(false); + } + + MoonriseCommon.IO_POOL.shutdown(false); + LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); + if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { + LOGGER.error("I/O pool did not shut down in time!"); + MoonriseCommon.IO_POOL.halt(false); + } + } + + private MoonriseCommon() {} +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java new file mode 100644 index 0000000000..559c959aff --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java @@ -0,0 +1,11 @@ +package ca.spottedleaf.moonrise.common.util; + +import ca.spottedleaf.moonrise.common.PlatformHooks; + +public final class MoonriseConstants { + + public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); + + private MoonriseConstants() {} + +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleThreadUnsafeRandom.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleThreadUnsafeRandom.java new file mode 100644 index 0000000000..8d57b9c141 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleThreadUnsafeRandom.java @@ -0,0 +1,105 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.BitRandomSource; +import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; + +/** + * Avoid costly CAS of superclass + division in nextInt + */ +public final class SimpleThreadUnsafeRandom implements BitRandomSource { + + private static final long MULTIPLIER = 25214903917L; + private static final long ADDEND = 11L; + private static final int BITS = 48; + private static final long MASK = (1L << BITS) - 1L; + + private long value; + private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this); + + public SimpleThreadUnsafeRandom(final long seed) { + this.setSeed(seed); + } + + @Override + public void setSeed(final long seed) { + this.value = (seed ^ MULTIPLIER) & MASK; + this.gaussianSource.reset(); + } + + private long advanceSeed() { + return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; + } + + @Override + public int next(final int bits) { + return (int)(this.advanceSeed() >>> (BITS - bits)); + } + + @Override + public int nextInt() { + final long seed = this.advanceSeed(); + return (int)(seed >>> (BITS - Integer.SIZE)); + } + + @Override + public int nextInt(final int bound) { + if (bound <= 0) { + throw new IllegalArgumentException(); + } + + // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); + return (int)((value * (long)bound) >>> Integer.SIZE); + } + + @Override + public double nextGaussian() { + return this.gaussianSource.nextGaussian(); + } + + @Override + public RandomSource fork() { + return new SimpleThreadUnsafeRandom(this.nextLong()); + } + + @Override + public PositionalRandomFactory forkPositional() { + return new SimpleRandomPositionalFactory(this.nextLong()); + } + + public static final class SimpleRandomPositionalFactory implements PositionalRandomFactory { + + private final long seed; + + public SimpleRandomPositionalFactory(final long seed) { + this.seed = seed; + } + + public long getSeed() { + return this.seed; + } + + @Override + public RandomSource fromHashOf(final String string) { + return new SimpleThreadUnsafeRandom((long)string.hashCode() ^ this.seed); + } + + @Override + public RandomSource fromSeed(final long seed) { + return new SimpleThreadUnsafeRandom(seed); + } + + @Override + public RandomSource at(final int x, final int y, final int z) { + return new SimpleThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed); + } + + @Override + public void parityConfigString(final StringBuilder stringBuilder) { + stringBuilder.append("SimpleRandomPositionalFactory{").append(this.seed).append('}'); + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java new file mode 100644 index 0000000000..12eb3add09 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java @@ -0,0 +1,94 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.BitRandomSource; +import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; + +/** + * Avoid costly CAS of superclass + */ +public final class ThreadUnsafeRandom implements BitRandomSource { + + private static final long MULTIPLIER = 25214903917L; + private static final long ADDEND = 11L; + private static final int BITS = 48; + private static final long MASK = (1L << BITS) - 1L; + + private long value; + private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this); + + public ThreadUnsafeRandom(final long seed) { + this.setSeed(seed); + } + + @Override + public void setSeed(final long seed) { + this.value = (seed ^ MULTIPLIER) & MASK; + this.gaussianSource.reset(); + } + + private long advanceSeed() { + return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; + } + + @Override + public int next(final int bits) { + return (int)(this.advanceSeed() >>> (BITS - bits)); + } + + @Override + public int nextInt() { + final long seed = this.advanceSeed(); + return (int)(seed >>> (BITS - Integer.SIZE)); + } + + @Override + public double nextGaussian() { + return this.gaussianSource.nextGaussian(); + } + + @Override + public RandomSource fork() { + return new ThreadUnsafeRandom(this.nextLong()); + } + + @Override + public PositionalRandomFactory forkPositional() { + return new ThreadUnsafeRandomPositionalFactory(this.nextLong()); + } + + public static final class ThreadUnsafeRandomPositionalFactory implements PositionalRandomFactory { + + private final long seed; + + public ThreadUnsafeRandomPositionalFactory(final long seed) { + this.seed = seed; + } + + public long getSeed() { + return this.seed; + } + + @Override + public RandomSource fromHashOf(final String string) { + return new ThreadUnsafeRandom((long)string.hashCode() ^ this.seed); + } + + @Override + public RandomSource fromSeed(final long seed) { + return new ThreadUnsafeRandom(seed); + } + + @Override + public RandomSource at(final int x, final int y, final int z) { + return new ThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed); + } + + @Override + public void parityConfigString(final StringBuilder stringBuilder) { + stringBuilder.append("ThreadUnsafeRandomPositionalFactory{").append(this.seed).append('}'); + } + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java new file mode 100644 index 0000000000..217d1f908a --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java @@ -0,0 +1,143 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +public class TickThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); + + /** + * @deprecated + */ + @Deprecated + public static void ensureTickThread(final String reason) { + if (!isTickThread()) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { + if (!isTickThreadFor(world, pos)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) { + if (!isTickThreadFor(world, pos)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { + if (!isTickThreadFor(world, chunkX, chunkZ)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Entity entity, final String reason) { + if (!isTickThreadFor(entity)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { + if (!isTickThreadFor(world, aabb)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { + if (!isTickThreadFor(world, blockX, blockZ)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + public TickThread(final String name) { + this(null, name); + } + + public TickThread(final Runnable run, final String name) { + this(null, run, name); + } + + public TickThread(final ThreadGroup group, final Runnable run, final String name) { + this(group, run, name, ID_GENERATOR.incrementAndGet()); + } + + private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { + super(group, run, name); + this.id = id; + } + + public static TickThread getCurrentTickThread() { + return (TickThread)Thread.currentThread(); + } + + public static boolean isTickThread() { + return Thread.currentThread() instanceof TickThread; + } + + public static boolean isShutdownThread() { + return false; + } + + public static boolean isTickThreadFor(final Level world, final BlockPos pos) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final Vec3 pos) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final AABB aabb) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { + return isTickThread(); + } + + public static boolean isTickThreadFor(final Entity entity) { + return isTickThread(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java new file mode 100644 index 0000000000..efda2688ae --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java @@ -0,0 +1,62 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; + +public final class WorldUtil { + + // min, max are inclusive + + public static int getMaxSection(final LevelHeightAccessor world) { + return world.getMaxSectionY(); + } + + public static int getMaxSection(final Level world) { + return world.getMaxSectionY(); + } + + public static int getMinSection(final LevelHeightAccessor world) { + return world.getMinSectionY(); + } + + public static int getMinSection(final Level world) { + return world.getMinSectionY(); + } + + public static int getMaxLightSection(final LevelHeightAccessor world) { + return getMaxSection(world) + 1; + } + + public static int getMinLightSection(final LevelHeightAccessor world) { + return getMinSection(world) - 1; + } + + + + public static int getTotalSections(final LevelHeightAccessor world) { + return getMaxSection(world) - getMinSection(world) + 1; + } + + public static int getTotalLightSections(final LevelHeightAccessor world) { + return getMaxLightSection(world) - getMinLightSection(world) + 1; + } + + public static int getMinBlockY(final LevelHeightAccessor world) { + return getMinSection(world) << 4; + } + + public static int getMaxBlockY(final LevelHeightAccessor world) { + return (getMaxSection(world) << 4) | 15; + } + + public static String getWorldName(final Level world) { + if (world == null) { + return "null world"; + } + return world.getWorld().getName(); // Paper + } + + private WorldUtil() { + throw new RuntimeException(); + } +} diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java new file mode 100644 index 0000000000..834c5ce238 --- /dev/null +++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java @@ -0,0 +1,240 @@ +package ca.spottedleaf.moonrise.paper; + +import ca.spottedleaf.moonrise.common.PlatformHooks; +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.DataFixer; +import com.mojang.serialization.Dynamic; +import java.util.Collection; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.boss.EnderDragonPart; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.phys.AABB; +import java.util.List; +import java.util.function.Predicate; + +public final class PaperHooks implements PlatformHooks { + + @Override + public String getBrand() { + return "Paper"; + } + + @Override + public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { + return blockState.getLightEmission(); + } + + @Override + public Predicate maybeHasLightEmission() { + return (final BlockState state) -> { + return state.getLightEmission() != 0; + }; + } + + @Override + public boolean hasCurrentlyLoadingChunk() { + return false; + } + + @Override + public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { + return null; + } + + @Override + public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { + + } + + @Override + public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { + + } + + @Override + public boolean allowAsyncTicketUpdates() { + return true; + } + + @Override + public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { + + } + + @Override + public void chunkUnloadFromWorld(final LevelChunk chunk) { + + } + + @Override + public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { + + } + + @Override + public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { + + } + + @Override + public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { + + } + + @Override + public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { + final Collection parts = world.dragonParts(); + if (parts.isEmpty()) { + return; + } + + for (final EnderDragonPart part : parts) { + if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) { + into.add(part); + } + } + } + + @Override + public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { + if (into.size() >= maxCount) { + // fix neoforge issue: do not add if list is already full + return; + } + + final Collection parts = world.dragonParts(); + if (parts.isEmpty()) { + return; + } + for (final EnderDragonPart part : parts) { + if (!part.getBoundingBox().intersects(boundingBox)) { + continue; + } + final T casted = (T)entityTypeTest.tryCast(part); + if (casted != null && (predicate == null || predicate.test(casted))) { + into.add(casted); + if (into.size() >= maxCount) { + break; + } + } + } + } + + @Override + public void entityMove(final Entity entity, final long oldSection, final long newSection) { + + } + + @Override + public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { + return true; + } + + @Override + public boolean configFixMC224294() { + return true; + } + + @Override + public boolean configAutoConfigSendDistance() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance; + } + + @Override + public double configPlayerMaxLoadRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; + } + + @Override + public double configPlayerMaxGenRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; + } + + @Override + public double configPlayerMaxSendRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; + } + + @Override + public int configPlayerMaxConcurrentLoads() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; + } + + @Override + public int configPlayerMaxConcurrentGens() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; + } + + @Override + public long configAutoSaveInterval(final ServerLevel world) { + return world.paperConfig().chunks.autoSaveInterval.value(); + } + + @Override + public int configMaxAutoSavePerTick(final ServerLevel world) { + return world.paperConfig().chunks.maxAutoSaveChunksPerTick; + } + + @Override + public boolean configFixMC159283() { + return true; + } + + @Override + public boolean forceNoSave(final ChunkAccess chunk) { + return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; + } + + @Override + public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, + final int fromVersion, final int toVersion) { + return (CompoundTag)dataFixer.update( + type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion + ).getValue(); + } + + @Override + public boolean hasMainChunkLoadHook() { + return false; + } + + @Override + public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { + + } + + @Override + public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { + return entities; + } + + @Override + public void unloadEntity(final Entity entity) { + entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); + } + + @Override + public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { + net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); + } + + @Override + public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { + return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/Metrics.java b/paper-server/src/main/java/com/destroystokyo/paper/Metrics.java new file mode 100644 index 0000000000..8f62879582 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/Metrics.java @@ -0,0 +1,682 @@ +package com.destroystokyo.paper; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.plugin.Plugin; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + * + * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + + // Executor service for requests + // We use an executor service because the Bukkit scheduler is affected by server lags + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/server-implementation"; + + // Should failed requests be logged? + private static boolean logFailedRequests = false; + + // The logger for the failed requests + private static Logger logger = Logger.getLogger("bStats"); + + // The name of the server software + private final String name; + + // The uuid of the server + private final String serverUUID; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param name The name of the server software. + * @param serverUUID The uuid of the server. + * @param logFailedRequests Whether failed requests should be logged or not. + * @param logger The logger for the failed requests. + */ + public Metrics(String name, String serverUUID, boolean logFailedRequests, Logger logger) { + this.name = name; + this.serverUUID = serverUUID; + Metrics.logFailedRequests = logFailedRequests; + Metrics.logger = logger; + + // Start submitting the data + startSubmitting(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Runnable submitTask = () -> { + if (!MinecraftServer.getServer().hasStopped()) { + submitData(); + } + }; + + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. + // WARNING: You must not modify any part of this Metrics class, including the submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + /** + * Gets the plugin specific data. + * + * @return The plugin specific data. + */ + private JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + data.put("pluginName", name); // Append the name of the server software + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // OS specific data + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + pluginData.add(getPluginData()); + data.put("plugins", pluginData); + + try { + // We are still in the Thread of the timer, so nothing get blocked :) + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + logger.log(Level.WARNING, "Could not submit stats of " + name, e); + } + } + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes("UTF-8")); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + public static class PaperMetrics { + public static void startMetrics() { + // Get the config file + File configFile = new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats"), "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + String serverUUID = config.getString("serverUuid"); + boolean logFailedRequests = config.getBoolean("logFailedRequests", false); + // Only start Metrics, if it's enabled in the config + if (config.getBoolean("enabled", true)) { + Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); + + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { + String minecraftVersion = Bukkit.getVersion(); + minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1); + return minecraftVersion; + })); + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); + final String paperVersion; + final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion(); + if (implVersion != null) { + final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1); + paperVersion = "git-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); + } else { + paperVersion = "unknown"; + } + metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion)); + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + // http://openjdk.java.net/jeps/223 + // Java decided to change their versioning scheme and in doing so modified the java.version system + // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier + // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+ + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it + // valid strings for the major may potentially include values such as -ea to deannotate a pre release + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + + return map; + })); + + metrics.addCustomChart(new Metrics.DrilldownPie("legacy_plugins", () -> { + Map> map = new HashMap<>(); + + // count legacy plugins + int legacy = 0; + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + if (CraftMagicNumbers.isLegacy(plugin.getDescription())) { + legacy++; + } + } + + // insert real value as lower dimension + Map entry = new HashMap<>(); + entry.put(String.valueOf(legacy), 1); + + // create buckets as higher dimension + if (legacy == 0) { + map.put("0 \uD83D\uDE0E", entry); // :sunglasses: + } else if (legacy <= 5) { + map.put("1-5", entry); + } else if (legacy <= 10) { + map.put("6-10", entry); + } else if (legacy <= 25) { + map.put("11-25", entry); + } else if (legacy <= 50) { + map.put("26-50", entry); + } else { + map.put("50+ \uD83D\uDE2D", entry); // :cry: + } + + return map; + })); + } + + } + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/PaperConfig.java b/paper-server/src/main/java/com/destroystokyo/paper/PaperConfig.java new file mode 100644 index 0000000000..ef41cf3a7d --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -0,0 +1,8 @@ +package com.destroystokyo.paper; + +/** + * @deprecated kept as a means to identify Paper in older plugins/PaperLib + */ +@Deprecated(forRemoval = true) +public class PaperConfig { +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/paper-server/src/main/java/com/destroystokyo/paper/PaperSkinParts.java new file mode 100644 index 0000000000..b6f4400df3 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/PaperSkinParts.java @@ -0,0 +1,74 @@ +package com.destroystokyo.paper; + +import com.google.common.base.Objects; + +import java.util.StringJoiner; + +public class PaperSkinParts implements SkinParts { + + private final int raw; + + public PaperSkinParts(int raw) { + this.raw = raw; + } + + public boolean hasCapeEnabled() { + return (raw & 1) == 1; + } + + public boolean hasJacketEnabled() { + return (raw >> 1 & 1) == 1; + } + + public boolean hasLeftSleeveEnabled() { + return (raw >> 2 & 1) == 1; + } + + public boolean hasRightSleeveEnabled() { + return (raw >> 3 & 1) == 1; + } + + public boolean hasLeftPantsEnabled() { + return (raw >> 4 & 1) == 1; + } + + public boolean hasRightPantsEnabled() { + return (raw >> 5 & 1) == 1; + } + + public boolean hasHatsEnabled() { + return (raw >> 6 & 1) == 1; + } + + @Override + public int getRaw() { + return raw; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PaperSkinParts that = (PaperSkinParts) o; + return raw == that.raw; + } + + @Override + public int hashCode() { + return Objects.hashCode(raw); + } + + @Override + public String toString() { + return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") + .add("raw=" + raw) + .add("cape=" + hasCapeEnabled()) + .add("jacket=" + hasJacketEnabled()) + .add("leftSleeve=" + hasLeftSleeveEnabled()) + .add("rightSleeve=" + hasRightSleeveEnabled()) + .add("leftPants=" + hasLeftPantsEnabled()) + .add("rightPants=" + hasRightPantsEnabled()) + .add("hats=" + hasHatsEnabled()) + .toString(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java new file mode 100644 index 0000000000..532306cacd --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -0,0 +1,146 @@ +package com.destroystokyo.paper; + +import com.destroystokyo.paper.util.VersionFetcher; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.mojang.logging.LogUtils; +import io.papermc.paper.ServerBuildInfo; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.StreamSupport; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.slf4j.Logger; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.TextColor.color; + +@DefaultQualifier(NonNull.class) +public class PaperVersionFetcher implements VersionFetcher { + private static final Logger LOGGER = LogUtils.getClassLogger(); + private static final int DISTANCE_ERROR = -1; + private static final int DISTANCE_UNKNOWN = -2; + private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; + + @Override + public long getCacheTime() { + return 720000; + } + + @Override + public Component getVersionMessage(final String serverVersion) { + final Component updateMessage; + final ServerBuildInfo build = ServerBuildInfo.buildInfo(); + if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { + updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); + } else { + updateMessage = getUpdateStatusMessage("PaperMC/Paper", build); + } + final @Nullable Component history = this.getHistory(); + + return history != null ? Component.textOfChildren(updateMessage, Component.newline(), history) : updateMessage; + } + + private static Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) { + int distance = DISTANCE_ERROR; + + final OptionalInt buildNumber = build.buildNumber(); + if (buildNumber.isPresent()) { + distance = fetchDistanceFromSiteApi(build, buildNumber.getAsInt()); + } else { + final Optional gitBranch = build.gitBranch(); + final Optional gitCommit = build.gitCommit(); + if (gitBranch.isPresent() && gitCommit.isPresent()) { + distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); + } + } + + return switch (distance) { + case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); + case 0 -> text("You are running the latest version", NamedTextColor.GREEN); + case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); + default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) + .append(Component.newline()) + .append(text("Download the new version at: ") + .append(text(DOWNLOAD_PAGE, NamedTextColor.GOLD) + .hoverEvent(text("Click to open", NamedTextColor.WHITE)) + .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); + }; + } + + private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { + try { + try (final BufferedReader reader = Resources.asCharSource( + URI.create("https://api.papermc.io/v2/projects/paper/versions/" + build.minecraftVersionId()).toURL(), + Charsets.UTF_8 + ).openBufferedStream()) { + final JsonObject json = new Gson().fromJson(reader, JsonObject.class); + final JsonArray builds = json.getAsJsonArray("builds"); + final int latest = StreamSupport.stream(builds.spliterator(), false) + .mapToInt(JsonElement::getAsInt) + .max() + .orElseThrow(); + return latest - jenkinsBuild; + } catch (final JsonSyntaxException ex) { + LOGGER.error("Error parsing json from Paper's downloads API", ex); + return DISTANCE_ERROR; + } + } catch (final IOException e) { + LOGGER.error("Error while parsing version", e); + return DISTANCE_ERROR; + } + } + + // Contributed by Techcable in GH-65 + private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) { + try { + final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/%s/compare/%s...%s".formatted(repo, branch, hash)).toURL().openConnection(); + connection.connect(); + if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return DISTANCE_UNKNOWN; // Unknown commit + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { + final JsonObject obj = new Gson().fromJson(reader, JsonObject.class); + final String status = obj.get("status").getAsString(); + return switch (status) { + case "identical" -> 0; + case "behind" -> obj.get("behind_by").getAsInt(); + default -> DISTANCE_ERROR; + }; + } catch (final JsonSyntaxException | NumberFormatException e) { + LOGGER.error("Error parsing json from GitHub's API", e); + return DISTANCE_ERROR; + } + } catch (final IOException e) { + LOGGER.error("Error while parsing version", e); + return DISTANCE_ERROR; + } + } + + private @Nullable Component getHistory() { + final VersionHistoryManager.@Nullable VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); + if (data == null) { + return null; + } + + final @Nullable String oldVersion = data.getOldVersion(); + if (oldVersion == null) { + return null; + } + + return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/paper-server/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java new file mode 100644 index 0000000000..c91f109b4c --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +1,8 @@ +package com.destroystokyo.paper; + +/** + * @deprecated kept as a means to identify Paper in older plugins/PaperLib + */ +@Deprecated(forRemoval = true) +public class PaperWorldConfig { +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java b/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java new file mode 100644 index 0000000000..f699ce18ca --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper; + +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.scheduler.CraftTask; +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerSchedulerException; + +/** + * 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; + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java new file mode 100644 index 0000000000..660b2ec6b6 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java @@ -0,0 +1,153 @@ +package com.destroystokyo.paper; + +import com.google.common.base.MoreObjects; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public enum VersionHistoryManager { + INSTANCE; + + private final Gson gson = new Gson(); + + private final Logger logger = Bukkit.getLogger(); + + private VersionData currentData = null; + + VersionHistoryManager() { + final Path path = Paths.get("version_history.json"); + + if (Files.exists(path)) { + // Basic file santiy checks + if (!Files.isRegularFile(path)) { + if (Files.isDirectory(path)) { + logger.severe(path + " is a directory, cannot be used for version history"); + } else { + logger.severe(path + " is not a regular file, cannot be used for version history"); + } + // We can't continue + return; + } + + try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + currentData = gson.fromJson(reader, VersionData.class); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); + return; + } catch (final JsonSyntaxException e) { + logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); + return; + } + + final String version = Bukkit.getVersion(); + if (version == null) { + logger.severe("Failed to retrieve current version"); + return; + } + + if (currentData == null) { + // Empty file + currentData = new VersionData(); + currentData.setCurrentVersion(version); + writeFile(path); + return; + } + + if (!version.equals(currentData.getCurrentVersion())) { + // The version appears to have changed + currentData.setOldVersion(currentData.getCurrentVersion()); + currentData.setCurrentVersion(version); + writeFile(path); + } + } else { + // File doesn't exist, start fresh + currentData = new VersionData(); + // oldVersion is null + currentData.setCurrentVersion(Bukkit.getVersion()); + writeFile(path); + } + } + + private void writeFile(@Nonnull final Path path) { + try (final BufferedWriter writer = Files.newBufferedWriter( + path, + StandardCharsets.UTF_8, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + )) { + gson.toJson(currentData, writer); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Failed to write to version history file", e); + } + } + + @Nullable + public VersionData getVersionData() { + return currentData; + } + + public static class VersionData { + private String oldVersion; + + private String currentVersion; + + @Nullable + public String getOldVersion() { + return oldVersion; + } + + public void setOldVersion(@Nullable String oldVersion) { + this.oldVersion = oldVersion; + } + + @Nullable + public String getCurrentVersion() { + return currentVersion; + } + + public void setCurrentVersion(@Nullable String currentVersion) { + this.currentVersion = currentVersion; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("oldVersion", oldVersion) + .add("currentVersion", currentVersion) + .toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final VersionData versionData = (VersionData) o; + return Objects.equals(oldVersion, versionData.oldVersion) && + Objects.equals(currentVersion, versionData.currentVersion); + } + + @Override + public int hashCode() { + return Objects.hash(oldVersion, currentVersion); + } + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java b/paper-server/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java new file mode 100644 index 0000000000..3913c407a3 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java @@ -0,0 +1,39 @@ +package com.destroystokyo.paper.block; + +import net.minecraft.world.level.block.SoundType; +import org.bukkit.Sound; +import org.bukkit.craftbukkit.CraftSound; + +@Deprecated(forRemoval = true) +public class CraftBlockSoundGroup implements BlockSoundGroup { + private final SoundType soundEffectType; + + public CraftBlockSoundGroup(SoundType soundEffectType) { + this.soundEffectType = soundEffectType; + } + + @Override + public Sound getBreakSound() { + return CraftSound.minecraftToBukkit(soundEffectType.getBreakSound()); + } + + @Override + public Sound getStepSound() { + return CraftSound.minecraftToBukkit(soundEffectType.getStepSound()); + } + + @Override + public Sound getPlaceSound() { + return CraftSound.minecraftToBukkit(soundEffectType.getPlaceSound()); + } + + @Override + public Sound getHitSound() { + return CraftSound.minecraftToBukkit(soundEffectType.getHitSound()); + } + + @Override + public Sound getFallSound() { + return CraftSound.minecraftToBukkit(soundEffectType.getFallSound()); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/paper-server/src/main/java/com/destroystokyo/paper/console/PaperConsole.java new file mode 100644 index 0000000000..6ee39b534b --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/console/PaperConsole.java @@ -0,0 +1,53 @@ +package com.destroystokyo.paper.console; + +import io.papermc.paper.configuration.GlobalConfiguration; +import io.papermc.paper.console.BrigadierCompletionMatcher; +import io.papermc.paper.console.BrigadierConsoleParser; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecrell.terminalconsole.SimpleTerminalConsole; +import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; + +public final class PaperConsole extends SimpleTerminalConsole { + + private final DedicatedServer server; + + public PaperConsole(DedicatedServer server) { + this.server = server; + } + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + builder + .appName("Paper") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + .option(LineReader.Option.COMPLETE_IN_WORD, true); + if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { + builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); + } + if (GlobalConfiguration.get().console.enableBrigadierCompletions) { + System.setProperty("org.jline.reader.support.parsedline", "true"); // to hide a warning message about the parser not supporting + builder.parser(new BrigadierConsoleParser(this.server)); + builder.completionMatcher(new BrigadierCompletionMatcher()); + } + return super.buildReader(builder); + } + + @Override + protected boolean isRunning() { + return !this.server.isStopped() && this.server.isRunning(); + } + + @Override + protected void runCommand(String command) { + this.server.handleConsoleInput(command, this.server.createCommandSourceStack()); + } + + @Override + protected void shutdown() { + this.server.halt(false); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/paper-server/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java new file mode 100644 index 0000000000..8f07539a82 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java @@ -0,0 +1,26 @@ +package com.destroystokyo.paper.console; + +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.apache.logging.log4j.LogManager; +import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; + +public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { + + private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); + + @Override + public void sendRawMessage(String message) { + final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); + this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); + } + + @Override + public void sendMessage(Identity identity, Component message, MessageType type) { + LOGGER.info(message); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java new file mode 100644 index 0000000000..d7a8eb1b8f --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/CraftRangedEntity.java @@ -0,0 +1,20 @@ +package com.destroystokyo.paper.entity; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.monster.RangedAttackMob; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.entity.LivingEntity; + +public interface CraftRangedEntity extends RangedEntity { + T getHandle(); + + @Override + default void rangedAttack(LivingEntity target, float charge) { + getHandle().performRangedAttack(((CraftLivingEntity) target).getHandle(), charge); + } + + @Override + default void setChargingAttack(boolean raiseHands) { + getHandle().setAggressive(raiseHands); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java new file mode 100644 index 0000000000..946cbc9556 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java @@ -0,0 +1,148 @@ +package com.destroystokyo.paper.entity; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.minecraft.world.level.pathfinder.Node; +import net.minecraft.world.level.pathfinder.Path; +import java.util.ArrayList; +import java.util.List; + +public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinder { + + private net.minecraft.world.entity.Mob entity; + + public PaperPathfinder(net.minecraft.world.entity.Mob entity) { + this.entity = entity; + } + + @Override + public Mob getEntity() { + return (Mob) entity.getBukkitEntity(); + } + + public void setHandle(net.minecraft.world.entity.Mob entity) { + this.entity = entity; + } + + @Override + public void stopPathfinding() { + entity.getNavigation().stop(); + } + + @Override + public boolean hasPath() { + return entity.getNavigation().getPath() != null && !entity.getNavigation().getPath().isDone(); + } + + @Nullable + @Override + public PathResult getCurrentPath() { + Path path = entity.getNavigation().getPath(); + return path != null && !path.isDone() ? new PaperPathResult(path) : null; + } + + @Nullable + @Override + public PathResult findPath(Location loc) { + Validate.notNull(loc, "Location can not be null"); + Path path = entity.getNavigation().createPath(loc.getX(), loc.getY(), loc.getZ(), 0); + return path != null ? new PaperPathResult(path) : null; + } + + @Nullable + @Override + public PathResult findPath(LivingEntity target) { + Validate.notNull(target, "Target can not be null"); + Path path = entity.getNavigation().createPath(((CraftLivingEntity) target).getHandle(), 0); + return path != null ? new PaperPathResult(path) : null; + } + + @Override + public boolean moveTo(@Nonnull PathResult path, double speed) { + Validate.notNull(path, "PathResult can not be null"); + Path pathEntity = ((PaperPathResult) path).path; + return entity.getNavigation().moveTo(pathEntity, speed); + } + + @Override + public boolean canOpenDoors() { + return entity.getNavigation().pathFinder.nodeEvaluator.canOpenDoors(); + } + + @Override + public void setCanOpenDoors(boolean canOpenDoors) { + entity.getNavigation().pathFinder.nodeEvaluator.setCanOpenDoors(canOpenDoors); + } + + @Override + public boolean canPassDoors() { + return entity.getNavigation().pathFinder.nodeEvaluator.canPassDoors(); + } + + @Override + public void setCanPassDoors(boolean canPassDoors) { + entity.getNavigation().pathFinder.nodeEvaluator.setCanPassDoors(canPassDoors); + } + + @Override + public boolean canFloat() { + return entity.getNavigation().pathFinder.nodeEvaluator.canFloat(); + } + + @Override + public void setCanFloat(boolean canFloat) { + entity.getNavigation().pathFinder.nodeEvaluator.setCanFloat(canFloat); + } + + public class PaperPathResult implements com.destroystokyo.paper.entity.PaperPathfinder.PathResult { + + private final Path path; + PaperPathResult(Path path) { + this.path = path; + } + + @Nullable + @Override + public Location getFinalPoint() { + Node point = path.getEndNode(); + return point != null ? toLoc(point) : null; + } + + @Override + public boolean canReachFinalPoint() { + return path.canReach(); + } + + @Override + public List getPoints() { + List points = new ArrayList<>(); + for (Node point : path.nodes) { + points.add(toLoc(point)); + } + return points; + } + + @Override + public int getNextPointIndex() { + return path.getNextNodeIndex(); + } + + @Nullable + @Override + public Location getNextPoint() { + if (!path.hasNext()) { + return null; + } + return toLoc(path.nodes.get(path.getNextNodeIndex())); + } + } + + private Location toLoc(Node point) { + return new Location(entity.level().getWorld(), point.x, point.y, point.z); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java new file mode 100644 index 0000000000..6bdc683b5a --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java @@ -0,0 +1,379 @@ +package com.destroystokyo.paper.entity.ai; + +import com.destroystokyo.paper.entity.RangedEntity; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import io.papermc.paper.util.ObfHelper; +import java.lang.reflect.Constructor; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import net.minecraft.world.entity.FlyingMob; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.TamableAnimal; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.ambient.AmbientCreature; +import net.minecraft.world.entity.animal.AbstractFish; +import net.minecraft.world.entity.animal.AbstractGolem; +import net.minecraft.world.entity.animal.AbstractSchoolingFish; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.Pufferfish; +import net.minecraft.world.entity.animal.ShoulderRidingEntity; +import net.minecraft.world.entity.animal.SnowGolem; +import net.minecraft.world.entity.animal.WaterAnimal; +import net.minecraft.world.entity.animal.camel.Camel; +import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; +import net.minecraft.world.entity.boss.wither.WitherBoss; +import net.minecraft.world.entity.monster.AbstractIllager; +import net.minecraft.world.entity.monster.EnderMan; +import net.minecraft.world.entity.monster.PatrollingMonster; +import net.minecraft.world.entity.monster.RangedAttackMob; +import net.minecraft.world.entity.monster.SpellcasterIllager; +import net.minecraft.world.entity.monster.ZombifiedPiglin; +import net.minecraft.world.entity.monster.breeze.Breeze; +import net.minecraft.world.entity.monster.piglin.AbstractPiglin; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.AbstractSkeleton; +import org.bukkit.entity.AbstractVillager; +import org.bukkit.entity.Ageable; +import org.bukkit.entity.Ambient; +import org.bukkit.entity.Animals; +import org.bukkit.entity.Bat; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Blaze; +import org.bukkit.entity.Cat; +import org.bukkit.entity.CaveSpider; +import org.bukkit.entity.ChestedHorse; +import org.bukkit.entity.Chicken; +import org.bukkit.entity.Cod; +import org.bukkit.entity.Cow; +import org.bukkit.entity.Creature; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Dolphin; +import org.bukkit.entity.Donkey; +import org.bukkit.entity.Drowned; +import org.bukkit.entity.ElderGuardian; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Endermite; +import org.bukkit.entity.Evoker; +import org.bukkit.entity.Fish; +import org.bukkit.entity.Flying; +import org.bukkit.entity.Fox; +import org.bukkit.entity.Ghast; +import org.bukkit.entity.Giant; +import org.bukkit.entity.Golem; +import org.bukkit.entity.Guardian; +import org.bukkit.entity.Hoglin; +import org.bukkit.entity.Horse; +import org.bukkit.entity.Husk; +import org.bukkit.entity.Illager; +import org.bukkit.entity.Illusioner; +import org.bukkit.entity.IronGolem; +import org.bukkit.entity.Llama; +import org.bukkit.entity.MagmaCube; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Mule; +import org.bukkit.entity.MushroomCow; +import org.bukkit.entity.Ocelot; +import org.bukkit.entity.Panda; +import org.bukkit.entity.Parrot; +import org.bukkit.entity.Phantom; +import org.bukkit.entity.Pig; +import org.bukkit.entity.PigZombie; +import org.bukkit.entity.Piglin; +import org.bukkit.entity.PiglinAbstract; +import org.bukkit.entity.PiglinBrute; +import org.bukkit.entity.Pillager; +import org.bukkit.entity.PolarBear; +import org.bukkit.entity.PufferFish; +import org.bukkit.entity.Rabbit; +import org.bukkit.entity.Raider; +import org.bukkit.entity.Ravager; +import org.bukkit.entity.Salmon; +import org.bukkit.entity.Sheep; +import org.bukkit.entity.Shulker; +import org.bukkit.entity.Silverfish; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.SkeletonHorse; +import org.bukkit.entity.Slime; +import org.bukkit.entity.Snowman; +import org.bukkit.entity.Spellcaster; +import org.bukkit.entity.Spider; +import org.bukkit.entity.Squid; +import org.bukkit.entity.Stray; +import org.bukkit.entity.Strider; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.TraderLlama; +import org.bukkit.entity.TropicalFish; +import org.bukkit.entity.Turtle; +import org.bukkit.entity.Vex; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Vindicator; +import org.bukkit.entity.WanderingTrader; +import org.bukkit.entity.WaterMob; +import org.bukkit.entity.Witch; +import org.bukkit.entity.Wither; +import org.bukkit.entity.WitherSkeleton; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.Zoglin; +import org.bukkit.entity.Zombie; +import org.bukkit.entity.ZombieHorse; +import org.bukkit.entity.ZombieVillager; + +public class MobGoalHelper { + + private static final BiMap deobfuscationMap = HashBiMap.create(); + private static final Map, Class> entityClassCache = new HashMap<>(); + private static final Map, Class> bukkitMap = new HashMap<>(); + + static final Set ignored = new HashSet<>(); + + static { + // TODO these kinda should be checked on each release, in case obfuscation changes + deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); + + ignored.add("goal_selector_1"); + ignored.add("goal_selector_2"); + ignored.add("selector_1"); + ignored.add("selector_2"); + ignored.add("wrapped"); + + bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); + bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); + bukkitMap.put(AmbientCreature.class, Ambient.class); + bukkitMap.put(Animal.class, Animals.class); + bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); + bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); + bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); + bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); + bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); + bukkitMap.put(PathfinderMob.class, Creature.class); + bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); + bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); + bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); + bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); + bukkitMap.put(EnderMan.class, Enderman.class); + bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); + bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); + bukkitMap.put(AbstractFish.class, Fish.class); + bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class); + bukkitMap.put(FlyingMob.class, Flying.class); + bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); + bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); + bukkitMap.put(AbstractGolem.class, Golem.class); + bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); + bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); + bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); + bukkitMap.put(Camel.class, org.bukkit.entity.Camel.class); + bukkitMap.put(AbstractIllager.class, Illager.class); + bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); + bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); + bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); + bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); + bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); + bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough + bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); + bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); + bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); + bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); + bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough + bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); + bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); + bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); + bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); + bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); + bukkitMap.put(Pufferfish.class, PufferFish.class); + bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); + bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); + bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); + bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); + bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); + bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); + bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); + bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); + bukkitMap.put(SnowGolem.class, Snowman.class); + bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); + bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); + bukkitMap.put(TamableAnimal.class, Tameable.class); + bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); + bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); + bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); + bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); + bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); + bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); + bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); + bukkitMap.put(WaterAnimal.class, WaterMob.class); + bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); + bukkitMap.put(WitherBoss.class, Wither.class); + bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); + bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); + bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); + bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); + bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); + bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); + bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); + bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); + bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); + bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); + bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); + bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); + bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); + bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); + bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); + bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); + bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); + bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, org.bukkit.entity.Sniffer.class); + bukkitMap.put(Breeze.class, org.bukkit.entity.Breeze.class); + bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, org.bukkit.entity.Armadillo.class); + bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, org.bukkit.entity.Bogged.class); + bukkitMap.put(net.minecraft.world.entity.monster.creaking.Creaking.class, org.bukkit.entity.Creaking.class); + bukkitMap.put(net.minecraft.world.entity.animal.AgeableWaterCreature.class, org.bukkit.entity.Squid.class); // close enough + } + + public static String getUsableName(Class clazz) { + String name = io.papermc.paper.util.MappingEnvironment.reobf() ? ObfHelper.INSTANCE.deobfClassName(clazz.getName()) : clazz.getName(); + name = name.substring(name.lastIndexOf(".") + 1); + boolean flag = false; + // inner classes + if (name.contains("$")) { + String cut = name.substring(name.indexOf("$") + 1); + if (cut.length() <= 2) { + name = name.replace("Entity", ""); + name = name.replace("$", "_"); + flag = true; + } else { + // mapped, wooo + name = cut; + } + } + name = name.replace("PathfinderGoal", ""); + name = name.replace("TargetGoal", ""); + name = name.replace("Goal", ""); + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) { + if (c >= 'A' && c <= 'Z') { + sb.append("_"); + sb.append(Character.toLowerCase(c)); + } else { + sb.append(c); + } + } + name = sb.toString(); + name = name.replaceFirst("_", ""); + + if (flag && !deobfuscationMap.containsKey(name.toLowerCase(java.util.Locale.ROOT)) && !ignored.contains(name)) { + System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase(java.util.Locale.ROOT) + ")"); + } + + // did we rename this key? + return deobfuscationMap.getOrDefault(name, name); + } + + public static EnumSet vanillaToPaper(Goal goal) { + EnumSet goals = EnumSet.noneOf(GoalType.class); + for (GoalType type : GoalType.values()) { + if (goal.hasFlag(paperToVanilla(type))) { + goals.add(type); + } + } + return goals; + } + + public static GoalType vanillaToPaper(Goal.Flag type) { + switch (type) { + case MOVE: + return GoalType.MOVE; + case LOOK: + return GoalType.LOOK; + case JUMP: + return GoalType.JUMP; + case UNKNOWN_BEHAVIOR: + return GoalType.UNKNOWN_BEHAVIOR; + case TARGET: + return GoalType.TARGET; + default: + throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); + } + } + + public static EnumSet paperToVanilla(EnumSet types) { + EnumSet goals = EnumSet.noneOf(Goal.Flag.class); + for (GoalType type : types) { + goals.add(paperToVanilla(type)); + } + return goals; + } + + public static Goal.Flag paperToVanilla(GoalType type) { + switch (type) { + case MOVE: + return Goal.Flag.MOVE; + case LOOK: + return Goal.Flag.LOOK; + case JUMP: + return Goal.Flag.JUMP; + case UNKNOWN_BEHAVIOR: + return Goal.Flag.UNKNOWN_BEHAVIOR; + case TARGET: + return Goal.Flag.TARGET; + default: + throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); + } + } + + public static GoalKey getKey(Class goalClass) { + String name = getUsableName(goalClass); + if (ignored.contains(name)) { + //noinspection unchecked + return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); + } + return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); + } + + public static Class getEntity(Class goalClass) { + //noinspection unchecked + return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { + for (Constructor ctor : key.getDeclaredConstructors()) { + for (int i = 0; i < ctor.getParameterCount(); i++) { + Class param = ctor.getParameterTypes()[i]; + if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { + //noinspection unchecked + return toBukkitClass((Class) param); + } else if (RangedAttackMob.class.isAssignableFrom(param)) { + return RangedEntity.class; + } + } + } + throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? + }); + } + + public static Class toBukkitClass(Class nmsClass) { + Class bukkitClass = bukkitMap.get(nmsClass); + if (bukkitClass == null) { + throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? + } + return bukkitClass; + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java new file mode 100644 index 0000000000..2625ff0d8f --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java @@ -0,0 +1,53 @@ +package com.destroystokyo.paper.entity.ai; + +import org.bukkit.entity.Mob; + +/** + * Wraps api in vanilla + */ +public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { + + private final Goal handle; + + public PaperCustomGoal(Goal handle) { + this.handle = handle; + + this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); + if (this.getFlags().size() == 0) { + this.addFlag(Flag.UNKNOWN_BEHAVIOR); + } + } + + @Override + public boolean canUse() { + return handle.shouldActivate(); + } + + @Override + public boolean canContinueToUse() { + return handle.shouldStayActive(); + } + + @Override + public void start() { + handle.start(); + } + + @Override + public void stop() { + handle.stop(); + } + + @Override + public void tick() { + handle.tick(); + } + + public Goal getHandle() { + return handle; + } + + public GoalKey getKey() { + return handle.getKey(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java new file mode 100644 index 0000000000..7e72fbb165 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java @@ -0,0 +1,226 @@ +package com.destroystokyo.paper.entity.ai; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import net.minecraft.world.entity.ai.goal.GoalSelector; +import net.minecraft.world.entity.ai.goal.WrappedGoal; +import org.bukkit.craftbukkit.entity.CraftMob; +import org.bukkit.entity.Mob; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class PaperMobGoals implements MobGoals { + + @Override + public void addGoal(T mob, int priority, Goal goal) { + CraftMob craftMob = (CraftMob) mob; + net.minecraft.world.entity.ai.goal.Goal mojangGoal; + + if (goal instanceof PaperVanillaGoal vanillaGoal) { + mojangGoal = vanillaGoal.getHandle(); + } else { + mojangGoal = new PaperCustomGoal<>(goal); + } + + getHandle(craftMob, goal.getTypes()).addGoal(priority, mojangGoal); + } + + @Override + public void removeGoal(T mob, Goal goal) { + CraftMob craftMob = (CraftMob) mob; + if (goal instanceof PaperCustomGoal) { + getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); + } else if (goal instanceof PaperVanillaGoal) { + getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); + } else { + List toRemove = new LinkedList<>(); + for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { + toRemove.add(item.getGoal()); + } + } + } + + for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { + getHandle(craftMob, goal.getTypes()).removeGoal(g); + } + } + } + + @Override + public void removeAllGoals(T mob) { + for (GoalType type : GoalType.values()) { + removeAllGoals(mob, type); + } + } + + @Override + public void removeAllGoals(T mob, GoalType type) { + for (Goal goal : getAllGoals(mob, type)) { + removeGoal(mob, goal); + } + } + + @Override + public void removeGoal(T mob, GoalKey key) { + for (Goal goal : getGoals(mob, key)) { + removeGoal(mob, goal); + } + } + + @Override + public boolean hasGoal(T mob, GoalKey key) { + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + return true; + } + } + return false; + } + + @Override + public Goal getGoal(T mob, GoalKey key) { + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + return g; + } + } + return null; + } + + @Override + public Collection> getGoals(T mob, GoalKey key) { + Set> goals = new HashSet<>(); + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + goals.add(g); + } + } + return goals; + } + + @Override + public Collection> getAllGoals(T mob) { + Set> goals = new HashSet<>(); + for (GoalType type : GoalType.values()) { + goals.addAll(getAllGoals(mob, type)); + } + return goals; + } + + @Override + public Collection> getAllGoals(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { + if (!item.getGoal().hasFlag(MobGoalHelper.paperToVanilla(type))) { + continue; + } + + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + } + return goals; + } + + @Override + public Collection> getAllGoalsWithout(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (GoalType internalType : GoalType.values()) { + if (internalType == type) { + continue; + } + for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { + if (item.getGoal().hasFlag(MobGoalHelper.paperToVanilla(type))) { + continue; + } + + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + } + } + return goals; + } + + @Override + public Collection> getRunningGoals(T mob) { + Set> goals = new HashSet<>(); + for (GoalType type : GoalType.values()) { + goals.addAll(getRunningGoals(mob, type)); + } + return goals; + } + + @Override + public Collection> getRunningGoals(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + getHandle(craftMob, type).getAvailableGoals() + .stream().filter(WrappedGoal::isRunning) + .filter(item -> item.getGoal().hasFlag(MobGoalHelper.paperToVanilla(type))) + .forEach(item -> { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + }); + return goals; + } + + @Override + public Collection> getRunningGoalsWithout(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (GoalType internalType : GoalType.values()) { + if (internalType == type) { + continue; + } + getHandle(craftMob, internalType).getAvailableGoals() + .stream() + .filter(WrappedGoal::isRunning) + .filter(item -> !item.getGoal().hasFlag(MobGoalHelper.paperToVanilla(type))) + .forEach(item -> { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + }); + } + return goals; + } + + private GoalSelector getHandle(CraftMob mob, EnumSet types) { + if (types.contains(GoalType.TARGET)) { + return mob.getHandle().targetSelector; + } else { + return mob.getHandle().goalSelector; + } + } + + private GoalSelector getHandle(CraftMob mob, GoalType type) { + if (type == GoalType.TARGET) { + return mob.getHandle().targetSelector; + } else { + return mob.getHandle().goalSelector; + } + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java new file mode 100644 index 0000000000..b5c594a549 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java @@ -0,0 +1,61 @@ +package com.destroystokyo.paper.entity.ai; + +import java.util.EnumSet; +import net.minecraft.world.entity.ai.goal.Goal; +import org.bukkit.entity.Mob; + +/** + * Wraps vanilla in api + */ +public class PaperVanillaGoal implements VanillaGoal { + + private final Goal handle; + private final GoalKey key; + + private final EnumSet types; + + public PaperVanillaGoal(Goal handle) { + this.handle = handle; + this.key = MobGoalHelper.getKey(handle.getClass()); + this.types = MobGoalHelper.vanillaToPaper(handle); + } + + public Goal getHandle() { + return handle; + } + + @Override + public boolean shouldActivate() { + return handle.canUse(); + } + + @Override + public boolean shouldStayActive() { + return handle.canContinueToUse(); + } + + @Override + public void start() { + handle.start(); + } + + @Override + public void stop() { + handle.stop(); + } + + @Override + public void tick() { + handle.tick(); + } + + @Override + public GoalKey getKey() { + return key; + } + + @Override + public EnumSet getTypes() { + return types; + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphColor.java b/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphColor.java new file mode 100644 index 0000000000..a4e641fdcc --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphColor.java @@ -0,0 +1,44 @@ +package com.destroystokyo.paper.gui; + +import java.awt.Color; + +public class GraphColor { + private static final Color[] colorLine = new Color[101]; + private static final Color[] colorFill = new Color[101]; + + static { + for (int i = 0; i < 101; i++) { + Color color = createColor(i); + colorLine[i] = new Color(color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 255); + colorFill[i] = new Color(colorLine[i].getRed(), colorLine[i].getGreen(), colorLine[i].getBlue(), 125); + } + } + + public static Color getLineColor(int percent) { + return colorLine[percent]; + } + + public static Color getFillColor(int percent) { + return colorFill[percent]; + } + + private static Color createColor(int percent) { + if (percent <= 50) { + return new Color(0X00FF00); + } + + int value = 510 - (int) (Math.min(Math.max(0, ((percent - 50) / 50F)), 1) * 510); + + int red, green; + if (value < 255) { + red = 255; + green = (int) (Math.sqrt(value) * 16); + } else { + green = 255; + value = value - 255; + red = 255 - (value * value / 255); + } + + return new Color(red, green, 0); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphData.java b/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphData.java new file mode 100644 index 0000000000..186fc72296 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/gui/GraphData.java @@ -0,0 +1,47 @@ +package com.destroystokyo.paper.gui; + +import java.awt.Color; + +public class GraphData { + private long total; + private long free; + private long max; + private long usedMem; + private int usedPercent; + + public GraphData(long total, long free, long max) { + this.total = total; + this.free = free; + this.max = max; + this.usedMem = total - free; + this.usedPercent = usedMem == 0 ? 0 : (int) (usedMem * 100L / max); + } + + public long getTotal() { + return total; + } + + public long getFree() { + return free; + } + + public long getMax() { + return max; + } + + public long getUsedMem() { + return usedMem; + } + + public int getUsedPercent() { + return usedPercent; + } + + public Color getFillColor() { + return GraphColor.getFillColor(usedPercent); + } + + public Color getLineColor() { + return GraphColor.getLineColor(usedPercent); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java b/paper-server/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java new file mode 100644 index 0000000000..537bc62135 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/gui/GuiStatsComponent.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.gui; + +import net.minecraft.server.MinecraftServer; + +import javax.swing.JPanel; +import javax.swing.Timer; +import java.awt.BorderLayout; +import java.awt.Dimension; + +public class GuiStatsComponent extends JPanel { + private final Timer timer; + private final RAMGraph ramGraph; + + public GuiStatsComponent(MinecraftServer server) { + super(new BorderLayout()); + + setOpaque(false); + + ramGraph = new RAMGraph(); + RAMDetails ramDetails = new RAMDetails(server); + + add(ramGraph, "North"); + add(ramDetails, "Center"); + + timer = new Timer(500, (event) -> { + ramGraph.update(); + ramDetails.update(); + }); + timer.start(); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 200); + } + + public void close() { + timer.stop(); + ramGraph.stop(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java new file mode 100644 index 0000000000..12b327eea9 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java @@ -0,0 +1,91 @@ +package com.destroystokyo.paper.gui; + +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.TimeUtil; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListSelectionModel; +import javax.swing.JList; +import javax.swing.border.EmptyBorder; +import java.awt.Dimension; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.Vector; + +public class RAMDetails extends JList { + public static final DecimalFormat DECIMAL_FORMAT = Util.make(new DecimalFormat("########0.000"), (format) + -> format.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT))); + + private final MinecraftServer server; + + public RAMDetails(MinecraftServer server) { + this.server = server; + + setBorder(new EmptyBorder(0, 10, 0, 0)); + setFixedCellHeight(20); + setOpaque(false); + + DefaultListCellRenderer renderer = new DefaultListCellRenderer(); + renderer.setOpaque(false); + setCellRenderer(renderer); + + setSelectionModel(new DefaultListSelectionModel() { + @Override + public void setAnchorSelectionIndex(final int anchorIndex) { + } + + @Override + public void setLeadAnchorNotificationEnabled(final boolean flag) { + } + + @Override + public void setLeadSelectionIndex(final int leadIndex) { + } + + @Override + public void setSelectionInterval(final int index0, final int index1) { + } + }); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 100); + } + + public void update() { + GraphData data = RAMGraph.DATA.peekLast(); + Vector vector = new Vector<>(); + + // Follows CraftServer#getTPS + double[] tps = new double[] { + server.tps1.getAverage(), + server.tps5.getAverage(), + server.tps15.getAverage() + }; + String[] tpsAvg = new String[tps.length]; + + for ( int g = 0; g < tps.length; g++) { + tpsAvg[g] = format( tps[g] ); + } + vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); + vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); + vector.add("Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms"); + vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); + setListData(vector); + } + + public double getAverage(long[] tickTimes) { + long total = 0L; + for (long value : tickTimes) { + total += value * 1000; + } + return ((double) total / (double) tickTimes.length) * 1.0E-6D; + } + + private static String format(double tps) { + return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java new file mode 100644 index 0000000000..a844669c57 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java @@ -0,0 +1,156 @@ +package com.destroystokyo.paper.gui; + +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.ToolTipManager; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.PointerInfo; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; + +public class RAMGraph extends JComponent { + public static final LinkedList DATA = new LinkedList() { + @Override + public boolean add(GraphData data) { + if (size() >= 348) { + remove(); + } + return super.add(data); + } + }; + + static { + GraphData empty = new GraphData(0, 0, 0); + for (int i = 0; i < 350; i++) { + DATA.add(empty); + } + } + + private final Timer timer; + private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); + + private int currentTick; + + public RAMGraph() { + ToolTipManager.sharedInstance().setInitialDelay(0); + + addMouseListener(new MouseAdapter() { + final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); + final int dismissDelayMinutes = (int) TimeUnit.MINUTES.toMillis(10); + + @Override + public void mouseEntered(MouseEvent me) { + ToolTipManager.sharedInstance().setDismissDelay(dismissDelayMinutes); + } + + @Override + public void mouseExited(MouseEvent me) { + ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); + } + }); + + timer = new Timer(50, (event) -> repaint()); + timer.start(); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(350, 110); + } + + public void update() { + Runtime jvm = Runtime.getRuntime(); + DATA.add(new GraphData(jvm.totalMemory(), jvm.freeMemory(), jvm.maxMemory())); + + PointerInfo pointerInfo = null; + // I think I recall spotting a bug report where this throwed an exception once + // not sure it's of concern here + try { + pointerInfo = MouseInfo.getPointerInfo(); + } catch (NullPointerException | ArrayIndexOutOfBoundsException ignored) { + // https://bugs.openjdk.org/browse/JDK-6840067 + } + if (pointerInfo != null) { + Point point = pointerInfo.getLocation(); + if (point != null) { + Point loc = new Point(point); + SwingUtilities.convertPointFromScreen(loc, this); + if (this.contains(loc)) { + ToolTipManager.sharedInstance().mouseMoved( + new MouseEvent(this, -1, System.currentTimeMillis(), 0, loc.x, loc.y, + point.x, point.y, 0, false, 0)); + } + } + } + + currentTick++; + } + + @Override + public void paint(Graphics graphics) { + graphics.setColor(new Color(0xFFFFFFFF)); + graphics.fillRect(0, 0, 350, 100); + + graphics.setColor(new Color(0x888888)); + graphics.drawLine(1, 25, 348, 25); + graphics.drawLine(1, 50, 348, 50); + graphics.drawLine(1, 75, 348, 75); + + int i = 0; + for (GraphData data : DATA) { + i++; + if ((i + currentTick) % 120 == 0) { + graphics.setColor(new Color(0x888888)); + graphics.drawLine(i, 1, i, 99); + } + int used = data.getUsedPercent(); + if (used > 0) { + Color color = data.getLineColor(); + graphics.setColor(data.getFillColor()); + graphics.fillRect(i, 100 - used, 1, used); + graphics.setColor(color); + graphics.fillRect(i, 100 - used, 1, 1); + } + } + + graphics.setColor(new Color(0xFF000000)); + graphics.drawRect(0, 0, 348, 100); + + Point m = null; + try { + m = getMousePosition(); + } catch (NullPointerException ignored) { + // https://bugs.openjdk.org/browse/JDK-6840067 + } + if (m != null && m.x > 0 && m.x < 348 && m.y > 0 && m.y < 100) { + GraphData data = DATA.get(m.x); + int used = data.getUsedPercent(); + graphics.setColor(new Color(0x000000)); + graphics.drawLine(m.x, 1, m.x, 99); + graphics.drawOval(m.x - 2, 100 - used - 2, 5, 5); + graphics.setColor(data.getLineColor()); + graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); + setToolTipText(String.format("Used: %s mb (%s%%)
%s", + Math.round(data.getUsedMem() / 1024F / 1024F), + used, getTime(m.x))); + } + } + + public String getTime(int halfSeconds) { + int millis = (348 - halfSeconds) / 2 * 1000; + return TIME_FORMAT.format(new Date((System.currentTimeMillis() - millis))); + } + + public void stop() { + timer.stop(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/paper-server/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java new file mode 100644 index 0000000000..605a4a83d0 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java @@ -0,0 +1,177 @@ +package com.destroystokyo.paper.io; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; + +public class SyncLoadFinder { + + public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads"); + + private static final WeakHashMap> SYNC_LOADS = new WeakHashMap<>(); + + private static final class SyncLoadInformation { + + public int times; + + public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); + } + + public static void clear() { + SYNC_LOADS.clear(); + } + + public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { + if (!ENABLED) { + return; + } + + final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); + + SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap map) -> { + if (map == null) { + map = new Object2ObjectOpenHashMap<>(); + } + + map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> { + if (valueInMap == null) { + valueInMap = new SyncLoadInformation(); + } + + ++valueInMap.times; + + valueInMap.coordinateTimes.compute(ChunkPos.asLong(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> { + return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1); + }); + + return valueInMap; + }); + + return map; + }); + } + + public static JsonObject serialize() { + final JsonObject ret = new JsonObject(); + + final JsonArray worldsData = new JsonArray(); + + for (final Map.Entry> entry : SYNC_LOADS.entrySet()) { + final Level world = entry.getKey(); + + final JsonObject worldData = new JsonObject(); + + worldData.addProperty("name", world.getWorld().getName()); + + final List> data = new ArrayList<>(); + + entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> { + data.add(new Pair<>(stacktrace, times)); + }); + + data.sort((Pair pair1, Pair pair2) -> { + return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order + }); + + final JsonArray stacktraces = new JsonArray(); + + for (Pair pair : data) { + final JsonObject stacktrace = new JsonObject(); + + stacktrace.addProperty("times", pair.getSecond().times); + + final JsonArray traces = new JsonArray(); + + for (StackTraceElement element : pair.getFirst().stacktrace) { + traces.add(String.valueOf(element)); + } + + stacktrace.add("stacktrace", traces); + + final JsonArray coordinates = new JsonArray(); + + for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) { + final long key = coordinate.getLongKey(); + final int times = coordinate.getIntValue(); + final ChunkPos chunkPos = new ChunkPos(key); + coordinates.add("(" + chunkPos.x + "," + chunkPos.z + "): " + times); + } + + stacktrace.add("coordinates", coordinates); + + stacktraces.add(stacktrace); + } + + + worldData.add("stacktraces", stacktraces); + worldsData.add(worldData); + } + + ret.add("worlds", worldsData); + + return ret; + } + + static final class ThrowableWithEquals { + + private final StackTraceElement[] stacktrace; + private final int hash; + + public ThrowableWithEquals(final StackTraceElement[] stacktrace) { + this.stacktrace = stacktrace; + this.hash = ThrowableWithEquals.hash(stacktrace); + } + + public static int hash(final StackTraceElement[] stacktrace) { + int hash = 0; + + for (int i = 0; i < stacktrace.length; ++i) { + hash *= 31; + hash += stacktrace[i].hashCode(); + } + + return hash; + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + + final ThrowableWithEquals other = (ThrowableWithEquals)obj; + final StackTraceElement[] otherStackTrace = other.stacktrace; + + if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) { + return false; + } + + if (this == obj) { + return true; + } + + for (int i = 0; i < this.stacktrace.length; ++i) { + if (!this.stacktrace[i].equals(otherStackTrace[i])) { + return false; + } + } + + return true; + } + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java new file mode 100644 index 0000000000..a53d51be1d --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java @@ -0,0 +1,21 @@ +package com.destroystokyo.paper.loottable; + +import org.bukkit.loot.LootTable; +import org.bukkit.loot.Lootable; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface PaperLootable extends Lootable { + + @Override + default void setLootTable(final @Nullable LootTable table) { + this.setLootTable(table, this.getSeed()); + } + + @Override + default void setSeed(final long seed) { + this.setLootTable(this.getLootTable(), seed); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java new file mode 100644 index 0000000000..9e9ea13234 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java @@ -0,0 +1,27 @@ +package com.destroystokyo.paper.loottable; + +import net.minecraft.world.RandomizableContainer; +import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.loot.LootTable; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface PaperLootableBlock extends PaperLootable { + + RandomizableContainer getRandomizableContainer(); + + /* Lootable */ + @Override + default @Nullable LootTable getLootTable() { + return CraftLootTable.minecraftToBukkit(this.getRandomizableContainer().getLootTable()); + } + + @Override + default void setLootTable(final @Nullable LootTable table, final long seed) { + this.getRandomizableContainer().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed); + } + + @Override + default long getSeed() { + return this.getRandomizableContainer().getLootTableSeed(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java new file mode 100644 index 0000000000..0699c60920 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java @@ -0,0 +1,26 @@ +package com.destroystokyo.paper.loottable; + +import java.util.Objects; +import net.minecraft.core.BlockPos; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory, PaperLootableBlock { + + /* PaperLootableInventory */ + @Override + default PaperLootableInventoryData lootableDataForAPI() { + return Objects.requireNonNull(this.getRandomizableContainer().lootableData(), "Can only manage loot tables on tile entities with lootableData"); + } + + /* LootableBlockInventory */ + @Override + default Block getBlock() { + final BlockPos position = this.getRandomizableContainer().getBlockPos(); + return CraftBlock.at(this.getNMSWorld(), position); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java new file mode 100644 index 0000000000..d933054535 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.loottable; + +import net.minecraft.world.entity.vehicle.ContainerEntity; +import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.loot.LootTable; +import org.bukkit.loot.Lootable; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface PaperLootableEntity extends Lootable { + + ContainerEntity getHandle(); + + /* Lootable */ + @Override + default @Nullable LootTable getLootTable() { + return CraftLootTable.minecraftToBukkit(this.getHandle().getContainerLootTable()); + } + + @Override + default void setLootTable(final @Nullable LootTable table, final long seed) { + this.getHandle().setContainerLootTable(CraftLootTable.bukkitToMinecraft(table)); + this.getHandle().setContainerLootTableSeed(seed); + } + + @Override + default long getSeed() { + return this.getHandle().getContainerLootTableSeed(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java new file mode 100644 index 0000000000..5c57acc95f --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java @@ -0,0 +1,26 @@ +package com.destroystokyo.paper.loottable; + +import net.minecraft.world.level.Level; +import org.bukkit.entity.Entity; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory, PaperLootableEntity { + + /* PaperLootableInventory */ + @Override + default Level getNMSWorld() { + return this.getHandle().level(); + } + + @Override + default PaperLootableInventoryData lootableDataForAPI() { + return this.getHandle().lootableData(); + } + + /* LootableEntityInventory */ + default Entity getEntity() { + return ((net.minecraft.world.entity.Entity) this.getHandle()).getBukkitEntity(); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java new file mode 100644 index 0000000000..9e7c22ef49 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java @@ -0,0 +1,79 @@ +package com.destroystokyo.paper.loottable; + +import java.util.UUID; +import net.minecraft.world.level.Level; +import org.bukkit.World; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface PaperLootableInventory extends PaperLootable, LootableInventory { + + /* impl */ + PaperLootableInventoryData lootableDataForAPI(); + + Level getNMSWorld(); + + default World getBukkitWorld() { + return this.getNMSWorld().getWorld(); + } + + /* LootableInventory */ + @Override + default boolean isRefillEnabled() { + return this.getNMSWorld().paperConfig().lootables.autoReplenish; + } + + @Override + default boolean hasBeenFilled() { + return this.getLastFilled() != -1; + } + + @Override + default boolean hasPlayerLooted(final UUID player) { + return this.lootableDataForAPI().hasPlayerLooted(player); + } + + @Override + default boolean canPlayerLoot(final UUID player) { + return this.lootableDataForAPI().canPlayerLoot(player, this.getNMSWorld().paperConfig()); + } + + @Override + default Long getLastLooted(final UUID player) { + return this.lootableDataForAPI().getLastLooted(player); + } + + @Override + default boolean setHasPlayerLooted(final UUID player, final boolean looted) { + final boolean hasLooted = this.hasPlayerLooted(player); + if (hasLooted != looted) { + this.lootableDataForAPI().setPlayerLootedState(player, looted); + } + return hasLooted; + } + + @Override + default boolean hasPendingRefill() { + final long nextRefill = this.lootableDataForAPI().getNextRefill(); + return nextRefill != -1 && nextRefill > this.lootableDataForAPI().getLastFill(); + } + + @Override + default long getLastFilled() { + return this.lootableDataForAPI().getLastFill(); + } + + @Override + default long getNextRefill() { + return this.lootableDataForAPI().getNextRefill(); + } + + @Override + default long setNextRefill(long refillAt) { + if (refillAt < -1) { + refillAt = -1; + } + return this.lootableDataForAPI().setNextRefill(refillAt); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java new file mode 100644 index 0000000000..861bff267c --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java @@ -0,0 +1,249 @@ +package com.destroystokyo.paper.loottable; + +import io.papermc.paper.configuration.WorldConfiguration; +import io.papermc.paper.configuration.type.DurationOrDisabled; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.world.RandomizableContainer; +import net.minecraft.world.entity.vehicle.ContainerEntity; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public class PaperLootableInventoryData { + + private static final Random RANDOM = new Random(); + + private long lastFill = -1; + private long nextRefill = -1; + private int numRefills = 0; + private @Nullable Map lootedPlayers; + + public long getLastFill() { + return this.lastFill; + } + + long getNextRefill() { + return this.nextRefill; + } + + long setNextRefill(final long nextRefill) { + final long prev = this.nextRefill; + this.nextRefill = nextRefill; + return prev; + } + + public boolean shouldReplenish(final T lootTableHolder, final LootTableInterface holderInterface, final net.minecraft.world.entity.player.@Nullable Player player) { + + // No Loot Table associated + if (!holderInterface.hasLootTable(lootTableHolder)) { + return false; + } + + // ALWAYS process the first fill or if the feature is disabled + if (this.lastFill == -1 || !holderInterface.paperConfig(lootTableHolder).lootables.autoReplenish) { + return true; + } + + // Only process refills when a player is set + if (player == null) { + return false; + } + + // Chest is not scheduled for refill + if (this.nextRefill == -1) { + return false; + } + + final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder); + + // Check if max refills has been hit + if (paperConfig.lootables.maxRefills != -1 && this.numRefills >= paperConfig.lootables.maxRefills) { + return false; + } + + // Refill has not been reached + if (this.nextRefill > System.currentTimeMillis()) { + return false; + } + + + final Player bukkitPlayer = (Player) player.getBukkitEntity(); + final LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, holderInterface.getInventoryForEvent(lootTableHolder)); + event.setCancelled(!this.canPlayerLoot(player.getUUID(), paperConfig)); + return event.callEvent(); + } + + public interface LootTableInterface { + + WorldConfiguration paperConfig(T holder); + + void setSeed(T holder, long seed); + + boolean hasLootTable(T holder); + + LootableInventory getInventoryForEvent(T holder); + } + + public static final LootTableInterface CONTAINER = new LootTableInterface<>() { + @Override + public WorldConfiguration paperConfig(final RandomizableContainer holder) { + return Objects.requireNonNull(holder.getLevel(), "Can only manager loot replenishment on block entities in a world").paperConfig(); + } + + @Override + public void setSeed(final RandomizableContainer holder, final long seed) { + holder.setLootTableSeed(seed); + } + + @Override + public boolean hasLootTable(final RandomizableContainer holder) { + return holder.getLootTable() != null; + } + + @Override + public LootableInventory getInventoryForEvent(final RandomizableContainer holder) { + return holder.getLootableInventory(); + } + }; + + public static final LootTableInterface ENTITY = new LootTableInterface<>() { + @Override + public WorldConfiguration paperConfig(final ContainerEntity holder) { + return holder.level().paperConfig(); + } + + @Override + public void setSeed(final ContainerEntity holder, final long seed) { + holder.setContainerLootTableSeed(seed); + } + + @Override + public boolean hasLootTable(final ContainerEntity holder) { + return holder.getContainerLootTable() != null; + } + + @Override + public LootableInventory getInventoryForEvent(final ContainerEntity holder) { + return holder.getLootableInventory(); + } + }; + + public boolean shouldClearLootTable(final T lootTableHolder, final LootTableInterface holderInterface, final net.minecraft.world.entity.player.@Nullable Player player) { + this.lastFill = System.currentTimeMillis(); + final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder); + if (paperConfig.lootables.autoReplenish) { + final long min = paperConfig.lootables.refreshMin.seconds(); + final long max = paperConfig.lootables.refreshMax.seconds(); + this.nextRefill = this.lastFill + (min + RANDOM.nextLong(max - min + 1)) * 1000L; + this.numRefills++; + if (paperConfig.lootables.resetSeedOnFill) { + holderInterface.setSeed(lootTableHolder, 0); + } + if (player != null) { // This means that numRefills can be incremented without a player being in the lootedPlayers list - Seems to be EntityMinecartChest specific + this.setPlayerLootedState(player.getUUID(), true); + } + return false; + } + return true; + } + + private static final String ROOT = "Paper.LootableData"; + private static final String LAST_FILL = "lastFill"; + private static final String NEXT_REFILL = "nextRefill"; + private static final String NUM_REFILLS = "numRefills"; + private static final String LOOTED_PLAYERS = "lootedPlayers"; + + public void loadNbt(final CompoundTag base) { + if (!base.contains(ROOT, Tag.TAG_COMPOUND)) { + return; + } + final CompoundTag comp = base.getCompound(ROOT); + if (comp.contains(LAST_FILL)) { + this.lastFill = comp.getLong(LAST_FILL); + } + if (comp.contains(NEXT_REFILL)) { + this.nextRefill = comp.getLong(NEXT_REFILL); + } + + if (comp.contains(NUM_REFILLS)) { + this.numRefills = comp.getInt(NUM_REFILLS); + } + if (comp.contains(LOOTED_PLAYERS, Tag.TAG_LIST)) { + final ListTag list = comp.getList(LOOTED_PLAYERS, Tag.TAG_COMPOUND); + final int size = list.size(); + if (size > 0) { + this.lootedPlayers = new HashMap<>(list.size()); + } + for (int i = 0; i < size; i++) { + final CompoundTag cmp = list.getCompound(i); + this.lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time")); + } + } + } + + public void saveNbt(final CompoundTag base) { + final CompoundTag comp = new CompoundTag(); + if (this.nextRefill != -1) { + comp.putLong(NEXT_REFILL, this.nextRefill); + } + if (this.lastFill != -1) { + comp.putLong(LAST_FILL, this.lastFill); + } + if (this.numRefills != 0) { + comp.putInt(NUM_REFILLS, this.numRefills); + } + if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) { + final ListTag list = new ListTag(); + for (final Map.Entry entry : this.lootedPlayers.entrySet()) { + final CompoundTag cmp = new CompoundTag(); + cmp.putUUID("UUID", entry.getKey()); + cmp.putLong("Time", entry.getValue()); + list.add(cmp); + } + comp.put(LOOTED_PLAYERS, list); + } + + if (!comp.isEmpty()) { + base.put(ROOT, comp); + } + } + + void setPlayerLootedState(final UUID player, final boolean looted) { + if (looted && this.lootedPlayers == null) { + this.lootedPlayers = new HashMap<>(); + } + if (looted) { + this.lootedPlayers.put(player, System.currentTimeMillis()); + } else if (this.lootedPlayers != null) { + this.lootedPlayers.remove(player); + } + } + + boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) { + final @Nullable Long lastLooted = this.getLastLooted(player); + if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true; + + final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime; + if (restrictPlayerRelootTime.value().isEmpty()) return false; + + return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis(); + } + + boolean hasPlayerLooted(final UUID player) { + return this.lootedPlayers != null && this.lootedPlayers.containsKey(player); + } + + @Nullable Long getLastLooted(final UUID player) { + return this.lootedPlayers != null ? this.lootedPlayers.get(player) : null; + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java new file mode 100644 index 0000000000..cc54b1c207 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java @@ -0,0 +1,75 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minecraft.ChatFormatting; +import net.minecraft.server.MinecraftServer; +import org.apache.commons.lang3.StringUtils; + +import java.net.InetSocketAddress; + +import javax.annotation.Nullable; + +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; + } + + @SuppressWarnings("deprecation") // Valid as this is the legacy status client + 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(PlainTextComponentSerializer.plainText().serialize(event.motd()), ChatFormatting.PREFIX_CODE)); + } + + private static String getFirstLine(String s) { + int pos = s.indexOf('\n'); + return pos >= 0 ? s.substring(0, pos) : s; + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java new file mode 100644 index 0000000000..a5a7624f1f --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java @@ -0,0 +1,49 @@ +package com.destroystokyo.paper.network; + +import java.net.InetSocketAddress; + +import javax.annotation.Nullable; +import net.minecraft.network.Connection; + +public class PaperNetworkClient implements NetworkClient { + + private final Connection networkManager; + + PaperNetworkClient(Connection 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/paper-server/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java new file mode 100644 index 0000000000..6ed2114f57 --- /dev/null +++ b/paper-server/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.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +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.motd(), server.getPlayerCount(), server.getMaxPlayers(), + server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon); + this.server = server; + } + + @Override + protected final Object[] getOnlinePlayers() { + return this.server.getPlayerList().players.toArray(); + } + + @Override + protected final Player getBukkitPlayer(Object player) { + return ((ServerPlayer) player).getBukkitEntity(); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java new file mode 100644 index 0000000000..d926ad8043 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java @@ -0,0 +1,11 @@ +package com.destroystokyo.paper.network; + +import net.minecraft.network.Connection; + +class PaperStatusClient extends PaperNetworkClient implements StatusClient { + + PaperStatusClient(Connection networkManager) { + super(networkManager); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/paper-server/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java new file mode 100644 index 0000000000..30a19d1086 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java @@ -0,0 +1,105 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.mojang.authlib.GameProfile; +import io.papermc.paper.adventure.AdventureComponent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket; +import net.minecraft.network.protocol.status.ServerStatus; +import net.minecraft.server.MinecraftServer; +import org.bukkit.craftbukkit.util.CraftIconCache; +import org.jetbrains.annotations.NotNull; + +public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { + + private List originalSample; + + private StandardPaperServerListPingEventImpl(MinecraftServer server, Connection networkManager, ServerStatus ping) { + super(server, new PaperStatusClient(networkManager), ping.version().map(ServerStatus.Version::protocol).orElse(-1), server.server.getServerIcon()); + this.originalSample = ping.players().map(ServerStatus.Players::sample).orElse(null); // GH-1473 - pre-tick race condition NPE + } + + @Nonnull + @Override + public List getListedPlayers() { + List sample = super.getListedPlayers(); + + if (this.originalSample != null) { + for (GameProfile profile : this.originalSample) { + sample.add(new ListedPlayerInfo(profile.getName(), profile.getId())); + } + this.originalSample = null; + } + + return sample; + } + + @Override + public @NotNull List getPlayerSample() { + this.getListedPlayers(); // Populate the backing list for the transforming view, and null out originalSample (see getListedPlayers and processRequest) + return super.getPlayerSample(); + } + + private List getPlayerSampleHandle() { + if (this.originalSample != null) { + return this.originalSample; + } + + List entries = super.getListedPlayers(); + if (entries.isEmpty()) { + return Collections.emptyList(); + } + + final List profiles = new ArrayList<>(); + for (ListedPlayerInfo playerInfo : entries) { + profiles.add(new GameProfile(playerInfo.id(), playerInfo.name())); + } + return profiles; + } + + public static void processRequest(MinecraftServer server, Connection networkManager) { + StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getStatus()); + server.server.getPluginManager().callEvent(event); + + // Close connection immediately if event is cancelled + if (event.isCancelled()) { + networkManager.disconnect((Component) null); + return; + } + + // Setup response + + // Description + final Component description = new AdventureComponent(event.motd()); + + // Players + final Optional players; + if (!event.shouldHidePlayers()) { + players = Optional.of(new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), event.getPlayerSampleHandle())); + } else { + players = Optional.empty(); + } + + // Version + final ServerStatus.Version version = new ServerStatus.Version(event.getVersion(), event.getProtocolVersion()); + + // Favicon + final Optional favicon; + if (event.getServerIcon() != null) { + favicon = Optional.of(new ServerStatus.Favicon(((CraftIconCache) event.getServerIcon()).value)); + } else { + favicon = Optional.empty(); + } + final ServerStatus ping = new ServerStatus(description, players, Optional.of(version), favicon, server.enforceSecureProfile()); + + // Send response + networkManager.send(new ClientboundStatusResponsePacket(ping)); + } + +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java new file mode 100644 index 0000000000..8849862b45 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java @@ -0,0 +1,451 @@ +package com.destroystokyo.paper.profile; + +import com.google.common.base.Preconditions; +import com.mojang.authlib.yggdrasil.ProfileResult; +import io.papermc.paper.configuration.GlobalConfiguration; +import com.google.common.base.Charsets; +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.util.StringUtil; +import net.minecraft.world.item.component.ResolvableProfile; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.profile.CraftPlayerTextures; +import org.bukkit.craftbukkit.profile.CraftProfileProperty; +import org.bukkit.profile.PlayerTextures; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +@SerializableAs("PlayerProfile") +public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { + + private boolean emptyName; + private boolean emptyUUID; + 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 = createAuthLibProfile(id, name); + this.emptyName = name == null; + this.emptyUUID = id == null; + } + + public CraftPlayerProfile(GameProfile profile) { + Validate.notNull(profile, "GameProfile cannot be null!"); + this.profile = profile; + } + + public CraftPlayerProfile(ResolvableProfile resolvableProfile) { + this(resolvableProfile.id().orElse(null), resolvableProfile.name().orElse(null)); + copyProfileProperties(resolvableProfile.gameProfile(), this.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); + + Preconditions.checkArgument(properties.size() < 16, "Cannot add more than 16 properties to a profile"); + properties.put(name, new Property(name, property.getValue(), property.getSignature())); + } + + @Override + public CraftPlayerTextures getTextures() { + return new CraftPlayerTextures(this); + } + + @Override + public void setTextures(@Nullable PlayerTextures textures) { + if (textures == null) { + this.removeProperty("textures"); + } else { + CraftPlayerTextures craftPlayerTextures = new CraftPlayerTextures(this); + craftPlayerTextures.copyFrom(textures); + craftPlayerTextures.rebuildPropertyIfDirty(); + } + } + + public GameProfile getGameProfile() { + return profile; + } + + @Nullable + @Override + public UUID getId() { + return this.emptyUUID ? null : this.profile.getId(); + } + + @Override + @Deprecated(forRemoval = true) + public UUID setId(@Nullable UUID uuid) { + final GameProfile previousProfile = this.profile; + final UUID previousId = this.getId(); + this.profile = createAuthLibProfile(uuid, previousProfile.getName()); + copyProfileProperties(previousProfile, this.profile); + this.emptyUUID = uuid == null; + return previousId; + } + + @Override + public UUID getUniqueId() { + return getId(); + } + + @Nullable + @Override + public String getName() { + return this.emptyName ? null : this.profile.getName(); + } + + @Override + @Deprecated(forRemoval = true) + public String setName(@Nullable String name) { + GameProfile prev = this.profile; + this.profile = createAuthLibProfile(prev.getId(), name); + copyProfileProperties(prev, this.profile); + this.emptyName = name == null; + 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(); + } + + @Nullable + @Override + public Property getProperty(String property) { + return Iterables.getFirst(this.profile.getProperties().get(property), null); + } + + @Nullable + @Override + public void setProperty(@NotNull String propertyName, @Nullable Property property) { + if (property != null) { + this.setProperty(new ProfileProperty(propertyName, property.value(), property.signature())); + } else { + profile.getProperties().removeAll(propertyName); + } + } + + @Override + public @NotNull GameProfile buildGameProfile() { + GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName()); + profile.getProperties().putAll(this.profile.getProperties()); + return profile; + } + + @Override + public @NotNull ResolvableProfile buildResolvableProfile() { + if (this.emptyName || this.emptyUUID) { + return new ResolvableProfile(this.emptyName ? Optional.empty() : Optional.of(this.profile.getName()), this.emptyUUID ? Optional.empty() : Optional.of(this.profile.getId()), this.profile.getProperties()); + } else { + return new ResolvableProfile(this.buildGameProfile()); + } + } + + @Override + public CraftPlayerProfile clone() { + CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); + clone.setProperties(getProperties()); + return clone; + } + + @Override + public boolean isComplete() { + return this.getId() != null && StringUtils.isNotBlank(this.getName()); + } + + @Override + public @NotNull CompletableFuture update() { + return CompletableFuture.supplyAsync(() -> { + final CraftPlayerProfile clone = clone(); + clone.complete(true); + return clone; + }, Util.PROFILE_EXECUTOR); + } + + @Override + public boolean completeFromCache() { + return completeFromCache(false, GlobalConfiguration.get().proxies.isProxyOnlineMode()); + } + + public boolean completeFromCache(boolean onlineMode) { + return completeFromCache(false, onlineMode); + } + + public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { + MinecraftServer server = MinecraftServer.getServer(); + String name = profile.getName(); + GameProfileCache userCache = server.getProfileCache(); + if (this.getId() == null) { + final GameProfile profile; + if (onlineMode) { + profile = lookupUUID ? userCache.get(name).orElse(null) : 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) { + // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't + copyProfileProperties(this.profile, profile); + this.profile = profile; + this.emptyUUID = false; // UUID was just retrieved from user cache and profile isn't null (so a completed profile was found) + } + } + + if ((profile.getName().isEmpty() || !hasTextures()) && this.getId() != null) { + Optional optProfile = userCache.get(this.profile.getId()); + if (optProfile.isPresent()) { + GameProfile profile = optProfile.get(); + if (this.profile.getName().isEmpty()) { + // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't + copyProfileProperties(this.profile, profile); + this.profile = profile; + this.emptyName = false; // Name was just retrieved via the userCache + } else if (profile != this.profile) { + copyProfileProperties(profile, this.profile); + } + } + } + return this.isComplete(); + } + + public boolean complete(boolean textures) { + return complete(textures, GlobalConfiguration.get().proxies.isProxyOnlineMode()); + } + + public boolean complete(boolean textures, boolean onlineMode) { + if (this.isComplete() && (!textures || hasTextures())) { // Don't do lookup if we already have everything + return true; + } + + MinecraftServer server = MinecraftServer.getServer(); + boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); + if (onlineMode && (!isCompleteFromCache || (textures && !hasTextures()))) { + ProfileResult result = server.getSessionService().fetchProfile(this.profile.getId(), true); + if (result != null && result.profile() != null) { + copyProfileProperties(result.profile(), this.profile, true); + } + if (this.isComplete()) { + server.getProfileCache().add(this.profile); + } + } + return this.isComplete() && (!onlineMode || !textures || hasTextures()); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target) { + copyProfileProperties(source, target, false); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { + if (source == target) { + throw new IllegalArgumentException("Source and target profiles are the same (" + source + ")"); + } + PropertyMap sourceProperties = source.getProperties(); + PropertyMap targetProperties = target.getProperties(); + if (clearTarget) targetProperties.clear(); + if (sourceProperties.isEmpty()) { + return; + } + + for (Property property : sourceProperties.values()) { + targetProperties.removeAll(property.name()); + targetProperties.put(property.name(), property); + } + } + + private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { + Preconditions.checkArgument(name == null || name.length() <= 16, "Name cannot be longer than 16 characters"); + Preconditions.checkArgument(name == null || StringUtil.isValidPlayerName(name), "The name of the profile contains invalid characters: %s", name); + return new GameProfile( + uniqueId != null ? uniqueId : Util.NIL_UUID, + name != null ? name : "" + ); + } + + private static ProfileProperty toBukkit(Property property) { + return new ProfileProperty(property.name(), property.value(), property.signature()); + } + + 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(); + } + + public static ResolvableProfile asResolvableProfileCopy(PlayerProfile profile) { + return ((SharedPlayerProfile) profile).buildResolvableProfile(); + } + + @Override + public @NotNull Map serialize() { + Map map = new LinkedHashMap<>(); + if (!this.emptyUUID) { + map.put("uniqueId", this.getId().toString()); + } + if (!this.emptyName) { + map.put("name", getName()); + } + if (!this.properties.isEmpty()) { + List propertiesData = new ArrayList<>(); + for (ProfileProperty property : properties) { + propertiesData.add(CraftProfileProperty.serialize(new Property(property.getName(), property.getValue(), property.getSignature()))); + } + map.put("properties", propertiesData); + } + return map; + } + + public static CraftPlayerProfile deserialize(Map map) { + UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); + String name = ConfigSerializationUtil.getString(map, "name", true); + + // This also validates the deserialized unique id and name (ensures that not both are null): + CraftPlayerProfile profile = new CraftPlayerProfile(uniqueId, name); + + if (map.containsKey("properties")) { + for (Object propertyData : (List) map.get("properties")) { + if (!(propertyData instanceof Map)) { + throw new IllegalArgumentException("Property data (" + propertyData + ") is not a valid Map"); + } + Property property = CraftProfileProperty.deserialize((Map) propertyData); + profile.profile.getProperties().put(property.name(), property); + } + } + + return profile; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + final CraftPlayerProfile that = (CraftPlayerProfile) o; + return this.emptyName == that.emptyName && this.emptyUUID == that.emptyUUID && Objects.equals(this.profile, that.profile); + } + + @Override + public int hashCode() { + return Objects.hash(this.emptyName, this.emptyUUID, this.profile); + } + + @Override + public String toString() { + return "CraftPlayerProfile [uniqueId=" + getId() + + ", name=" + getName() + + ", properties=" + org.bukkit.craftbukkit.profile.CraftPlayerProfile.toString(this.profile.getProperties()) + + "]"; + } + + 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/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java new file mode 100644 index 0000000000..48e774677e --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java @@ -0,0 +1,30 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Environment; +import com.mojang.authlib.EnvironmentParser; +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; + +import java.net.Proxy; + +public class PaperAuthenticationService extends YggdrasilAuthenticationService { + + private final Environment environment; + + public PaperAuthenticationService(Proxy proxy) { + super(proxy); + this.environment = EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD.getEnvironment()); + } + + @Override + public MinecraftSessionService createMinecraftSessionService() { + return new PaperMinecraftSessionService(this.getServicesKeySet(), this.getProxy(), this.environment); + } + + @Override + public GameProfileRepository createProfileRepository() { + return new PaperGameProfileRepository(this.getProxy(), this.environment); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java new file mode 100644 index 0000000000..b2ad0c4d92 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java @@ -0,0 +1,60 @@ +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.Environment; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.ProfileLookupCallback; +import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; +import java.net.Proxy; +import java.util.Set; + +public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { + public PaperGameProfileRepository(Proxy proxy, Environment environment) { + super(proxy, environment); + } + + @Override + public void findProfilesByNames(String[] names, ProfileLookupCallback callback) { + Set unfoundNames = Sets.newHashSet(); + for (String name : names) { + PreLookupProfileEvent event = new PreLookupProfileEvent(name); + event.callEvent(); + if (event.getUUID() != null) { + // Plugin provided UUID, 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, new PreProfileLookupCallback(callback)); + } + } + + private record PreProfileLookupCallback(ProfileLookupCallback callback) implements ProfileLookupCallback { + @Override + public void onProfileLookupSucceeded(GameProfile gameProfile) { + PlayerProfile from = CraftPlayerProfile.asBukkitMirror(gameProfile); + new LookupProfileEvent(from).callEvent(); + this.callback.onProfileLookupSucceeded(gameProfile); + } + + @Override + public void onProfileLookupFailed(final String profileName, final Exception exception) { + this.callback.onProfileLookupFailed(profileName, exception); + } + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java new file mode 100644 index 0000000000..d577384797 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java @@ -0,0 +1,37 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Environment; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.yggdrasil.ProfileResult; +import com.mojang.authlib.yggdrasil.ServicesKeySet; +import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; + +import java.net.Proxy; +import java.util.UUID; +import org.jetbrains.annotations.Nullable; + +public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { + + protected PaperMinecraftSessionService(ServicesKeySet servicesKeySet, Proxy proxy, Environment environment) { + super(servicesKeySet, proxy, environment); + } + + public @Nullable ProfileResult fetchProfile(GameProfile profile, final boolean requireSecure) { + CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile); + new com.destroystokyo.paper.event.profile.PreFillProfileEvent(playerProfile).callEvent(); + profile = playerProfile.getGameProfile(); + if (profile.getProperties().containsKey("textures")) { + return new ProfileResult(profile, java.util.Collections.emptySet()); + } + ProfileResult result = super.fetchProfile(profile.getId(), requireSecure); + if (result != null) { + new com.destroystokyo.paper.event.profile.FillProfileEvent(CraftPlayerProfile.asBukkitMirror(result.profile())).callEvent(); + } + return result; + } + + @Override @io.papermc.paper.annotation.DoNotUse @Deprecated + public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) { + return super.fetchProfile(profileId, requireSecure); + } +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java new file mode 100644 index 0000000000..332700f84c --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java @@ -0,0 +1,26 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import net.minecraft.world.item.component.ResolvableProfile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public interface SharedPlayerProfile { + + @Nullable UUID getUniqueId(); + + @Nullable String getName(); + + boolean removeProperty(@NotNull String property); + + @Nullable Property getProperty(@NotNull String propertyName); + + @Nullable void setProperty(@NotNull String propertyName, @Nullable Property property); + + @NotNull GameProfile buildGameProfile(); + + @NotNull ResolvableProfile buildResolvableProfile(); +} diff --git a/paper-server/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/paper-server/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java new file mode 100644 index 0000000000..1b79795535 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java @@ -0,0 +1,86 @@ +package com.destroystokyo.paper.proxy; + +import io.papermc.paper.configuration.GlobalConfiguration; +import com.google.common.net.InetAddresses; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import java.net.InetAddress; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.login.custom.CustomQueryPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.ProfilePublicKey; + +/** + * While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users + * have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. + * Further, the BungeeCord IP forwarding protocol still retains essentially its original + * form, when there is brand-new support for custom login plugin messages in 1.13. + *

+ * Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity + * of messages, is packed into a binary format that is smaller than BungeeCord's + * forwarding, and is integrated into the Minecraft login process by using the 1.13 + * login plugin message packet. + */ +public class VelocityProxy { + private static final int SUPPORTED_FORWARDING_VERSION = 1; + public static final int MODERN_FORWARDING_WITH_KEY = 2; + public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; + public static final int MODERN_LAZY_SESSION = 4; + public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; + public static final ResourceLocation PLAYER_INFO_CHANNEL = ResourceLocation.fromNamespaceAndPath("velocity", "player_info"); + + public static boolean checkIntegrity(final FriendlyByteBuf buf) { + final byte[] signature = new byte[32]; + buf.readBytes(signature); + + final byte[] data = new byte[buf.readableBytes()]; + buf.getBytes(buf.readerIndex(), data); + + try { + final Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); + final byte[] mySignature = mac.doFinal(data); + if (!MessageDigest.isEqual(signature, mySignature)) { + return false; + } + } catch (final InvalidKeyException | NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + return true; + } + + public static InetAddress readAddress(final FriendlyByteBuf buf) { + return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); + } + + public static GameProfile createProfile(final FriendlyByteBuf buf) { + final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); + readProperties(buf, profile); + return profile; + } + + private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { + final int properties = buf.readVarInt(); + for (int i1 = 0; i1 < properties; i1++) { + final String name = buf.readUtf(Short.MAX_VALUE); + final String value = buf.readUtf(Short.MAX_VALUE); + final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; + profile.getProperties().put(name, new Property(name, value, signature)); + } + } + + public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { + return new ProfilePublicKey.Data(buf); + } + + public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { + return buf.readBoolean() ? buf.readUUID() : orElse; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/CraftGameEventTag.java b/paper-server/src/main/java/io/papermc/paper/CraftGameEventTag.java new file mode 100644 index 0000000000..874c420e60 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/CraftGameEventTag.java @@ -0,0 +1,35 @@ +package io.papermc.paper; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import org.bukkit.GameEvent; +import org.bukkit.craftbukkit.tag.CraftTag; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class CraftGameEventTag extends CraftTag { + + public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { + super(registry, tag); + } + + private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); + @Override + public boolean isTagged(@NotNull GameEvent gameEvent) { + return registry.getOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); + } + + @Override + public @NotNull Set getValues() { + return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java b/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java new file mode 100644 index 0000000000..cfd47bcdcd --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java @@ -0,0 +1,72 @@ +package io.papermc.paper; + +import io.papermc.paper.command.PaperSubcommand; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectSets; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.minecraft.core.Registry; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import org.bukkit.Chunk; +import org.bukkit.World; + +public final class FeatureHooks { + + public static void initChunkTaskScheduler(final boolean useParallelGen) { + } + + public static void registerPaperCommands(final Map, PaperSubcommand> commands) { + } + + public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { + return new LevelChunkSection(biomeRegistry); + } + + public static void sendChunkRefreshPackets(final List playersInRange, final LevelChunk chunk) { + final ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, chunk.level.getLightEngine(), null, null); + for (final ServerPlayer player : playersInRange) { + if (player.connection == null) continue; + + player.connection.send(refreshPacket); + } + } + + public static PalettedContainer emptyPalettedBlockContainer() { + return new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); + } + + public static Set getSentChunkKeys(final ServerPlayer player) { + final LongSet keys = new LongOpenHashSet(); + player.getChunkTrackingView().forEach(pos -> keys.add(pos.longKey)); + return LongSets.unmodifiable(keys); + } + + public static Set getSentChunks(final ServerPlayer player) { + final ObjectSet chunks = new ObjectOpenHashSet<>(); + final World world = player.serverLevel().getWorld(); + player.getChunkTrackingView().forEach(pos -> { + final org.bukkit.Chunk chunk = world.getChunkAt(pos.longKey); + chunks.add(chunk); + }); + return ObjectSets.unmodifiable(chunks); + } + + public static boolean isChunkSent(final ServerPlayer player, final long chunkKey) { + return player.getChunkTrackingView().contains(new ChunkPos(chunkKey)); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java b/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java new file mode 100644 index 0000000000..d543b1b107 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java @@ -0,0 +1,55 @@ +package io.papermc.paper; + +import java.util.List; +import joptsimple.OptionSet; +import net.minecraft.SharedConstants; +import net.minecraft.server.Main; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class PaperBootstrap { + private static final Logger LOGGER = LoggerFactory.getLogger("bootstrap"); + + private PaperBootstrap() { + } + + public static void boot(final OptionSet options) { + SharedConstants.tryDetectVersion(); + + getStartupVersionMessages().forEach(LOGGER::info); + + Main.main(options); + } + + private static List getStartupVersionMessages() { + final String javaSpecVersion = System.getProperty("java.specification.version"); + final String javaVmName = System.getProperty("java.vm.name"); + final String javaVmVersion = System.getProperty("java.vm.version"); + final String javaVendor = System.getProperty("java.vendor"); + final String javaVendorVersion = System.getProperty("java.vendor.version"); + final String osName = System.getProperty("os.name"); + final String osVersion = System.getProperty("os.version"); + final String osArch = System.getProperty("os.arch"); + + final ServerBuildInfo bi = ServerBuildInfo.buildInfo(); + return List.of( + String.format( + "Running Java %s (%s %s; %s %s) on %s %s (%s)", + javaSpecVersion, + javaVmName, + javaVmVersion, + javaVendor, + javaVendorVersion, + osName, + osVersion, + osArch + ), + String.format( + "Loading %s %s for Minecraft %s", + bi.brandName(), + bi.asString(ServerBuildInfo.StringRepresentation.VERSION_FULL), + bi.minecraftVersionId() + ) + ); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java new file mode 100644 index 0000000000..790bad0494 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java @@ -0,0 +1,104 @@ +package io.papermc.paper; + +import com.google.common.base.Strings; +import io.papermc.paper.util.JarManifests; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.jar.Manifest; +import net.kyori.adventure.key.Key; +import net.minecraft.SharedConstants; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.Main; +import org.jetbrains.annotations.NotNull; + +public record ServerBuildInfoImpl( + Key brandId, + String brandName, + String minecraftVersionId, + String minecraftVersionName, + OptionalInt buildNumber, + Instant buildTime, + Optional gitBranch, + Optional gitCommit +) implements ServerBuildInfo { + private static final String ATTRIBUTE_BRAND_ID = "Brand-Id"; + private static final String ATTRIBUTE_BRAND_NAME = "Brand-Name"; + private static final String ATTRIBUTE_BUILD_TIME = "Build-Time"; + private static final String ATTRIBUTE_BUILD_NUMBER = "Build-Number"; + private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; + private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; + + private static final String BRAND_PAPER_NAME = "Paper"; + + private static final String BUILD_DEV = "DEV"; + + public ServerBuildInfoImpl() { + this(JarManifests.manifest(CraftServer.class)); + } + + private ServerBuildInfoImpl(final Manifest manifest) { + this( + getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) + .map(Key::key) + .orElse(BRAND_PAPER_ID), + getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) + .orElse(BRAND_PAPER_NAME), + SharedConstants.getCurrentVersion().getId(), + SharedConstants.getCurrentVersion().getName(), + getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) + .map(Integer::parseInt) + .map(OptionalInt::of) + .orElse(OptionalInt.empty()), + getManifestAttribute(manifest, ATTRIBUTE_BUILD_TIME) + .map(Instant::parse) + .orElse(Main.BOOT_TIME), + getManifestAttribute(manifest, ATTRIBUTE_GIT_BRANCH), + getManifestAttribute(manifest, ATTRIBUTE_GIT_COMMIT) + ); + } + + @Override + public boolean isBrandCompatible(final @NotNull Key brandId) { + return brandId.equals(this.brandId); + } + + @Override + public @NotNull String asString(final @NotNull StringRepresentation representation) { + final StringBuilder sb = new StringBuilder(); + sb.append(this.minecraftVersionId); + sb.append('-'); + if (this.buildNumber.isPresent()) { + sb.append(this.buildNumber.getAsInt()); + } else { + sb.append(BUILD_DEV); + } + final boolean hasGitBranch = this.gitBranch.isPresent(); + final boolean hasGitCommit = this.gitCommit.isPresent(); + if (hasGitBranch || hasGitCommit) { + sb.append('-'); + } + if (hasGitBranch && representation == StringRepresentation.VERSION_FULL) { + sb.append(this.gitBranch.get()); + if (hasGitCommit) { + sb.append('@'); + } + } + if (hasGitCommit) { + sb.append(this.gitCommit.get()); + } + if (representation == StringRepresentation.VERSION_FULL) { + sb.append(' '); + sb.append('('); + sb.append(this.buildTime.truncatedTo(ChronoUnit.SECONDS)); + sb.append(')'); + } + return sb.toString(); + } + + private static Optional getManifestAttribute(final Manifest manifest, final String name) { + final String value = manifest != null ? manifest.getMainAttributes().getValue(name) : null; + return Optional.ofNullable(Strings.emptyToNull(value)); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/SparksFly.java b/paper-server/src/main/java/io/papermc/paper/SparksFly.java new file mode 100644 index 0000000000..62e2d5704c --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/SparksFly.java @@ -0,0 +1,211 @@ +package io.papermc.paper; + +import io.papermc.paper.configuration.GlobalConfiguration; +import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage; +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; +import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; +import io.papermc.paper.util.MCUtil; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.lucko.spark.paper.api.Compatibility; +import me.lucko.spark.paper.api.PaperClassLookup; +import me.lucko.spark.paper.api.PaperScheduler; +import me.lucko.spark.paper.api.PaperSparkModule; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import net.minecraft.util.ExceptionCollector; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; + +// It's like electricity. +public final class SparksFly { + public static final String ID = "spark"; + public static final String COMMAND_NAME = "spark"; + + private static final String PREFER_SPARK_PLUGIN_PROPERTY = "paper.preferSparkPlugin"; + + private static final int SPARK_YELLOW = 0xffc93a; + + private final Logger logger; + private final PaperSparkModule spark; + private final ConcurrentLinkedQueue mainThreadTaskQueue; + + private boolean enabled; + private boolean disabledInConfigurationWarningLogged; + + public SparksFly(final Server server) { + this.mainThreadTaskQueue = new ConcurrentLinkedQueue<>(); + this.logger = Logger.getLogger(ID); + this.logger.log(Level.INFO, "This server bundles the spark profiler. For more information please visit https://docs.papermc.io/paper/profiling"); + this.spark = PaperSparkModule.create(Compatibility.VERSION_1_0, server, this.logger, new PaperScheduler() { + @Override + public void executeAsync(final Runnable runnable) { + MCUtil.scheduleAsyncTask(this.catching(runnable, "asynchronous")); + } + + @Override + public void executeSync(final Runnable runnable) { + SparksFly.this.mainThreadTaskQueue.offer(this.catching(runnable, "synchronous")); + } + + private Runnable catching(final Runnable runnable, final String type) { + return () -> { + try { + runnable.run(); + } catch (final Throwable t) { + SparksFly.this.logger.log(Level.SEVERE, "An exception was encountered while executing a " + type + " spark task", t); + } + }; + } + }, new PaperClassLookup() { + @Override + public Class lookup(final String className) throws Exception { + final ExceptionCollector exceptions = new ExceptionCollector<>(); + try { + return Class.forName(className); + } catch (final ClassNotFoundException e) { + exceptions.add(e); + for (final ConfiguredPluginClassLoader loader : ((PaperPluginClassLoaderStorage) PaperClassLoaderStorage.instance()).getGlobalGroup().getClassLoaders()) { + try { + final Class loadedClass = loader.loadClass(className, true, false, true); + if (loadedClass != null) { + return loadedClass; + } + } catch (final ClassNotFoundException exception) { + exceptions.add(exception); + } + } + exceptions.throwIfPresent(); + return null; + } + } + }); + } + + public void executeMainThreadTasks() { + Runnable task; + while ((task = this.mainThreadTaskQueue.poll()) != null) { + task.run(); + } + } + + public void enableEarlyIfRequested() { + if (!isPluginPreferred() && shouldEnableImmediately()) { + this.enable(); + } + } + + public void enableBeforePlugins() { + if (!isPluginPreferred()) { + this.enable(); + } + } + + public void enableAfterPlugins(final Server server) { + final boolean isPluginPreferred = isPluginPreferred(); + final boolean isPluginEnabled = isPluginEnabled(server); + if (!isPluginPreferred || !isPluginEnabled) { + if (isPluginPreferred && !this.enabled) { + this.logger.log(Level.INFO, "The spark plugin has been preferred but was not loaded. The bundled spark profiler will enabled instead."); + } + this.enable(); + } + } + + private void enable() { + if (!this.enabled) { + if (GlobalConfiguration.get().spark.enabled) { + this.enabled = true; + this.spark.enable(); + } else { + if (!this.disabledInConfigurationWarningLogged) { + this.logger.log(Level.INFO, "The spark profiler will not be enabled because it is currently disabled in the configuration."); + this.disabledInConfigurationWarningLogged = true; + } + } + } + } + + public void disable() { + if (this.enabled) { + this.spark.disable(); + this.enabled = false; + } + } + + public void registerCommandBeforePlugins(final Server server) { + if (!isPluginPreferred()) { + this.registerCommand(server); + } + } + + public void registerCommandAfterPlugins(final Server server) { + if ((!isPluginPreferred() || !isPluginEnabled(server)) && server.getCommandMap().getCommand(COMMAND_NAME) == null) { + this.registerCommand(server); + } + } + + private void registerCommand(final Server server) { + server.getCommandMap().register(COMMAND_NAME, "paper", new CommandImpl(COMMAND_NAME, this.spark.getPermissions())); + } + + public void tickStart() { + this.spark.onServerTickStart(); + } + + public void tickEnd(final double duration) { + this.spark.onServerTickEnd(duration); + } + + void executeCommand(final CommandSender sender, final String[] args) { + this.spark.executeCommand(sender, args); + } + + List tabComplete(final CommandSender sender, final String[] args) { + return this.spark.tabComplete(sender, args); + } + + public static boolean isPluginPreferred() { + return Boolean.getBoolean(PREFER_SPARK_PLUGIN_PROPERTY); + } + + private static boolean isPluginEnabled(final Server server) { + return server.getPluginManager().isPluginEnabled(ID); + } + + private static boolean shouldEnableImmediately() { + return GlobalConfiguration.get().spark.enableImmediately; + } + + public static final class CommandImpl extends Command { + CommandImpl(final String name, final Collection permissions) { + super(name); + this.setPermission(String.join(";", permissions)); + } + + @Override + public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) { + final SparksFly spark = ((CraftServer) sender.getServer()).spark; + if (spark.enabled) { + spark.executeCommand(sender, args); + } else { + sender.sendMessage(Component.text("The spark profiler is currently disabled.", TextColor.color(SPARK_YELLOW))); + } + return true; + } + + @Override + public List tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException { + final SparksFly spark = ((CraftServer) sender.getServer()).spark; + if (spark.enabled) { + return spark.tabComplete(sender, args); + } + return List.of(); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java b/paper-server/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java new file mode 100644 index 0000000000..adac21ce6d --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java @@ -0,0 +1,69 @@ +package io.papermc.paper.advancement; + +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementType; +import net.minecraft.advancements.DisplayInfo; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record PaperAdvancementDisplay(DisplayInfo handle) implements AdvancementDisplay { + + @Override + public @NotNull Frame frame() { + return asPaperFrame(this.handle.getType()); + } + + @Override + public @NotNull Component title() { + return PaperAdventure.asAdventure(this.handle.getTitle()); + } + + @Override + public @NotNull Component description() { + return PaperAdventure.asAdventure(this.handle.getDescription()); + } + + @Override + public @NotNull ItemStack icon() { + return CraftItemStack.asBukkitCopy(this.handle.getIcon()); + } + + @Override + public boolean doesShowToast() { + return this.handle.shouldShowToast(); + } + + @Override + public boolean doesAnnounceToChat() { + return this.handle.shouldAnnounceChat(); + } + + @Override + public boolean isHidden() { + return this.handle.isHidden(); + } + + @Override + public @Nullable NamespacedKey backgroundPath() { + return this.handle.getBackground().map(CraftNamespacedKey::fromMinecraft).orElse(null); + } + + @Override + public @NotNull Component displayName() { + return PaperAdventure.asAdventure(Advancement.decorateName(java.util.Objects.requireNonNull(this.handle, "cannot build display name for null handle, invalid state"))); + } + + public static @NotNull Frame asPaperFrame(final @NotNull AdvancementType frameType) { + return switch (frameType) { + case TASK -> Frame.TASK; + case CHALLENGE -> Frame.CHALLENGE; + case GOAL -> Frame.GOAL; + }; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java new file mode 100644 index 0000000000..2c5702a42c --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java @@ -0,0 +1,450 @@ +package io.papermc.paper.adventure; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Either; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.BlockNBTComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.EntityNBTComponent; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.NBTComponent; +import net.kyori.adventure.text.NBTComponentBuilder; +import net.kyori.adventure.text.ScoreComponent; +import net.kyori.adventure.text.SelectorComponent; +import net.kyori.adventure.text.StorageNBTComponent; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.DataComponentValue; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minecraft.commands.arguments.selector.SelectorPattern; +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.chat.contents.KeybindContents; +import net.minecraft.network.chat.contents.ScoreContents; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.RegistryOps; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.intellij.lang.annotations.Subst; + +import static com.mojang.serialization.Codec.recursive; +import static com.mojang.serialization.codecs.RecordCodecBuilder.mapCodec; +import static java.util.function.Function.identity; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.TranslationArgument.bool; +import static net.kyori.adventure.text.TranslationArgument.component; +import static net.kyori.adventure.text.TranslationArgument.numeric; + +@DefaultQualifier(NonNull.class) +public final class AdventureCodecs { + + public static final Codec COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec); + public static final StreamCodec STREAM_COMPONENT_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(COMPONENT_CODEC); + + static final Codec TEXT_COLOR_CODEC = Codec.STRING.comapFlatMap(s -> { + if (s.startsWith("#")) { + @Nullable TextColor value = TextColor.fromHexString(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure TextColor"); + } else { + final @Nullable NamedTextColor value = NamedTextColor.NAMES.value(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure NamedTextColor"); + } + }, textColor -> { + if (textColor instanceof NamedTextColor named) { + return NamedTextColor.NAMES.keyOrThrow(named); + } else { + return textColor.asHexString(); + } + }); + + static final Codec KEY_CODEC = Codec.STRING.comapFlatMap(s -> { + return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key"); + }, Key::asString); + + static final Codec CLICK_EVENT_ACTION_CODEC = Codec.STRING.comapFlatMap(s -> { + final ClickEvent.@Nullable Action value = ClickEvent.Action.NAMES.value(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure ClickEvent$Action"); + }, ClickEvent.Action.NAMES::keyOrThrow); + static final Codec CLICK_EVENT_CODEC = RecordCodecBuilder.create((instance) -> { + return instance.group( + CLICK_EVENT_ACTION_CODEC.fieldOf("action").forGetter(ClickEvent::action), + Codec.STRING.fieldOf("value").forGetter(ClickEvent::value) + ).apply(instance, ClickEvent::clickEvent); + }); + + static Codec showEntityCodec(final Codec componentCodec) { + return RecordCodecBuilder.create((instance) -> { + return instance.group( + KEY_CODEC.fieldOf("type").forGetter(HoverEvent.ShowEntity::type), + UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(HoverEvent.ShowEntity::id), + componentCodec.lenientOptionalFieldOf("name").forGetter(he -> Optional.ofNullable(he.name())) + ).apply(instance, (key, uuid, component) -> { + return HoverEvent.ShowEntity.showEntity(key, uuid, component.orElse(null)); + }); + }); + } + + static Codec showItemCodec(final Codec componentCodec) { + return net.minecraft.network.chat.HoverEvent.ItemStackInfo.CODEC.xmap(isi -> { + @Subst("key") final String typeKey = isi.item.unwrapKey().orElseThrow().location().toString(); + return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asAdventure(isi.getItemStack().getComponentsPatch())); + }, si -> { + final Item itemType = BuiltInRegistries.ITEM.getValue(PaperAdventure.asVanilla(si.item())); + final Map dataComponentsMap = si.dataComponents(); + final ItemStack stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), PaperAdventure.asVanilla(dataComponentsMap)); + return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack); + }); + } + + static final HoverEventType SHOW_ENTITY_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showEntityCodec, HoverEvent.Action.SHOW_ENTITY, "show_entity", AdventureCodecs::legacyDeserializeEntity); + static final HoverEventType SHOW_ITEM_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showItemCodec, HoverEvent.Action.SHOW_ITEM, "show_item", AdventureCodecs::legacyDeserializeItem); + static final HoverEventType SHOW_TEXT_HOVER_EVENT_TYPE = new HoverEventType<>(identity(), HoverEvent.Action.SHOW_TEXT, "show_text", (component, registryOps, codec) -> DataResult.success(component)); + static final Codec> HOVER_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new HoverEventType[]{ SHOW_ENTITY_HOVER_EVENT_TYPE, SHOW_ITEM_HOVER_EVENT_TYPE, SHOW_TEXT_HOVER_EVENT_TYPE }); + + static DataResult legacyDeserializeEntity(final Component component, final @Nullable RegistryOps ops, final Codec componentCodec) { + try { + final CompoundTag tag = TagParser.parseTag(PlainTextComponentSerializer.plainText().serialize(component)); + final DynamicOps dynamicOps = ops != null ? ops.withParent(JsonOps.INSTANCE) : JsonOps.INSTANCE; + final DataResult entityNameResult = componentCodec.parse(dynamicOps, JsonParser.parseString(tag.getString("name"))); + @Subst("key") final String keyString = tag.getString("type"); + final UUID entityUUID = UUID.fromString(tag.getString("id")); + return entityNameResult.map(name -> HoverEvent.ShowEntity.showEntity(Key.key(keyString), entityUUID, name)); + } catch (final Exception ex) { + return DataResult.error(() -> "Failed to parse tooltip: " + ex.getMessage()); + } + } + + static DataResult legacyDeserializeItem(final Component component, final @Nullable RegistryOps ops, final Codec componentCodec) { + try { + final CompoundTag tag = TagParser.parseTag(PlainTextComponentSerializer.plainText().serialize(component)); + final DynamicOps dynamicOps = ops != null ? ops.withParent(NbtOps.INSTANCE) : NbtOps.INSTANCE; + final DataResult stackResult = ItemStack.CODEC.parse(dynamicOps, tag); + return stackResult.map(stack -> { + @Subst("key:value") final String location = stack.getItemHolder().unwrapKey().orElseThrow().location().toString(); + return HoverEvent.ShowItem.showItem(Key.key(location), stack.getCount(), PaperAdventure.asAdventure(stack.getComponentsPatch())); + }); + } catch (final CommandSyntaxException ex) { + return DataResult.error(() -> "Failed to parse item tag: " + ex.getMessage()); + } + } + + @FunctionalInterface + interface LegacyDeserializer { + DataResult apply(Component component, @Nullable RegistryOps ops, Codec componentCodec); + } + + record HoverEventType(Function, MapCodec>> codec, String id, Function, MapCodec>> legacyCodec) implements StringRepresentable { + HoverEventType(final Function, Codec> contentCodec, final HoverEvent.Action action, final String id, final LegacyDeserializer legacyDeserializer) { + this(cc -> contentCodec.apply(cc).xmap(v -> HoverEvent.hoverEvent(action, v), HoverEvent::value).fieldOf("contents"), + id, + codec -> (new Codec>() { + public DataResult, D>> decode(final DynamicOps dynamicOps, final D object) { + return codec.decode(dynamicOps, object).flatMap(pair -> { + final DataResult dataResult; + if (dynamicOps instanceof final RegistryOps registryOps) { + dataResult = legacyDeserializer.apply(pair.getFirst(), registryOps, codec); + } else { + dataResult = legacyDeserializer.apply(pair.getFirst(), null, codec); + } + + return dataResult.map(value -> Pair.of(HoverEvent.hoverEvent(action, value), pair.getSecond())); + }); + } + + public DataResult encode(final HoverEvent hoverEvent, final DynamicOps dynamicOps, final D object) { + return DataResult.error(() -> "Can't encode in legacy format"); + } + }).fieldOf("value") + ); + } + @Override + public String getSerializedName() { + return this.id; + } + } + + private static final Function, HoverEventType> GET_HOVER_EVENT_TYPE = he -> { + if (he.action() == HoverEvent.Action.SHOW_ENTITY) { + return SHOW_ENTITY_HOVER_EVENT_TYPE; + } else if (he.action() == HoverEvent.Action.SHOW_ITEM) { + return SHOW_ITEM_HOVER_EVENT_TYPE; + } else if (he.action() == HoverEvent.Action.SHOW_TEXT) { + return SHOW_TEXT_HOVER_EVENT_TYPE; + } else { + throw new IllegalStateException(); + } + }; + static final Codec> HOVER_EVENT_CODEC = Codec.withAlternative( + HOVER_EVENT_TYPE_CODEC.>dispatchMap("action", GET_HOVER_EVENT_TYPE, het -> het.codec.apply(COMPONENT_CODEC)).codec(), + HOVER_EVENT_TYPE_CODEC.>dispatchMap("action", GET_HOVER_EVENT_TYPE, het -> het.legacyCodec.apply(COMPONENT_CODEC)).codec() + ); + + public static final MapCodec