commit faf2de70aa431fcdd0a8c17211b4e8a9dba19354 Author: Alexander Medvedev Date: Tue Apr 14 19:56:27 2026 +0200 first commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..979f4b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,677 @@ + 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 international 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 modifying 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 the + Corresponding Source is located. 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 if 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 surcharge for exercising rights granted under this +License, and you may not impose any (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 any recipient 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/README.md b/README.md new file mode 100644 index 0000000..4527687 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Pumpkin Plugin API for Python + +This package provides everything needed to write a Pumpkin server plugin compiled to WebAssembly using Python. + +## Quick start + +1. Install `pumpkin-api-py`: + +```bash +pip install pumpkin-api-py +``` + +2. Create your plugin (`main.py`): + +```python +from pumpkin_api import ( + Plugin, PluginMetadata, register_plugin, + server, event, command, text, context +) + +class MyPlugin(Plugin): + def metadata(self) -> PluginMetadata: + return PluginMetadata( + name="my-plugin", + version="0.1.0", + authors=["you"], + description="An example plugin." + ) + + def on_load(self, ctx: context.Context) -> None: + print("Python plugin loaded!") + + # Register an event handler + self.register_event(ctx, event.EventType.PLAYER_JOIN_EVENT, self.on_player_join) + + def on_player_join(self, srv: server.Server, evt: event.PlayerJoinEventData) -> event.PlayerJoinEventData: + print(f"Player {evt.player.get_name()} joined!") + return evt + +register_plugin(MyPlugin) +``` + +3. Build your plugin into a WebAssembly component: + +```bash +pumpkin-api-build main -o my_plugin.wasm +``` diff --git a/example/main.py b/example/main.py new file mode 100644 index 0000000..15e55c7 --- /dev/null +++ b/example/main.py @@ -0,0 +1,36 @@ +from pumpkin_api import ( + Plugin, PluginMetadata, register_plugin, + server, event, command, text, context +) +from pumpkin_api.app import WitWorld, Metadata + +class MyPlugin(Plugin): + def metadata(self) -> PluginMetadata: + return PluginMetadata( + name="my-plugin", + version="0.1.0", + authors=["you"], + description="An example python plugin." + ) + + def on_load(self, ctx: context.Context) -> None: + print("Python plugin loaded!") + + # Register an event handler + self.register_event(ctx, event.EventType.PLAYER_JOIN_EVENT, self.on_player_join) + + # Register a command + # cmd = command.Command(["hello"], "A simple hello command") + # self.register_command(ctx, cmd, self.hello_command) + + def on_player_join(self, srv: server.Server, evt: event.PlayerJoinEventData) -> event.PlayerJoinEventData: + print(f"Player {evt.player.get_name()} joined!") + # We can modify the join message + # evt.join_message = text.TextComponent_text("Welcome to the server!") + return evt + + def hello_command(self, sender: command.CommandSender, srv: server.Server, args: command.ConsumedArgs) -> int: + sender.send_message(text.TextComponent_text("Hello from Python!")) + return 1 + +register_plugin(MyPlugin) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..26dccc5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "pumpkin-api-py" +version = "0.1.0.dev0" +description = "Pumpkin plugin API for Python" +authors = [{name = "alex"}] +license = "GPL-3.0-only" +dependencies = [ + "componentize-py>=0.22.1", +] +readme = "README.md" +requires-python = ">=3.9" + +[project.scripts] +pumpkin-api-build = "pumpkin_api.build:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/pumpkin_api"] +include = ["src/pumpkin_api/wit_files/**/*.wit"] diff --git a/src/pumpkin_api/__init__.py b/src/pumpkin_api/__init__.py new file mode 100644 index 0000000..aeacb39 --- /dev/null +++ b/src/pumpkin_api/__init__.py @@ -0,0 +1,139 @@ +# pumpkin-api-py: Pumpkin plugin API for Python +# Copyright (C) 2024 alex +# +# 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 . + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'wit')) + +from typing import Dict, Callable, Any, Type, Optional, List, Union + +from .wit.wit_world.imports import ( + server, event, command, context, text, scheduler, + logging, gui, scoreboard, i18n, player, world, + common, entity, permission +) +from .wit.wit_world.exports import metadata +from componentize_py_types import Err + +class PluginMetadata(metadata.PluginMetadata): + pass + +_PLUGIN_INSTANCE: Optional['Plugin'] = None +_EVENT_HANDLERS: Dict[int, Callable[[server.Server, Any], Any]] = {} +_COMMAND_HANDLERS: Dict[int, Callable[[command.CommandSender, server.Server, command.ConsumedArgs], int]] = {} +_TASK_HANDLERS: Dict[int, Callable[[server.Server], None]] = {} +_NEXT_HANDLER_ID: int = 0 + +def _get_next_handler_id() -> int: + global _NEXT_HANDLER_ID + handler_id = _NEXT_HANDLER_ID + _NEXT_HANDLER_ID += 1 + return handler_id + +class Plugin: + def __init__(self): + self._pending_events = [] + + def metadata(self) -> PluginMetadata: + raise NotImplementedError + + def on_load(self, ctx: context.Context) -> None: + for event_type, handler, priority, blocking in self._pending_events: + self.register_event(ctx, event_type, handler, priority, blocking) + + def on_unload(self, ctx: context.Context) -> None: + pass + + def register_event(self, ctx: context.Context, event_type: event.EventType, handler: Callable[[server.Server, Any], Any], priority=event.EventPriority.NORMAL, blocking=True): + handler_id = _get_next_handler_id() + _EVENT_HANDLERS[handler_id] = handler + ctx.register_event(handler_id, event_type, priority, blocking) + + def register_command(self, ctx: context.Context, cmd: command.Command, handler: Callable[[command.CommandSender, server.Server, command.ConsumedArgs], int], permission: str = ""): + handler_id = _get_next_handler_id() + _COMMAND_HANDLERS[handler_id] = handler + cmd.execute_with_handler_id(handler_id) + ctx.register_command(cmd, permission) + + def schedule_delayed_task(self, delay_ticks: int, handler: Callable[[server.Server], None]) -> int: + handler_id = _get_next_handler_id() + _TASK_HANDLERS[handler_id] = handler + return scheduler.schedule_delayed_task(handler_id, delay_ticks) + + def schedule_repeating_task(self, delay_ticks: int, period_ticks: int, handler: Callable[[server.Server], None]) -> int: + handler_id = _get_next_handler_id() + _TASK_HANDLERS[handler_id] = handler + return scheduler.schedule_repeating_task(handler_id, delay_ticks, period_ticks) + + def event_handler(self, event_type: event.EventType, priority=event.EventPriority.NORMAL, blocking=True): + def decorator(func): + self._pending_events.append((event_type, func, priority, blocking)) + return func + return decorator + +def register_plugin(plugin_class: Type[Plugin]): + global _PLUGIN_INSTANCE + _PLUGIN_INSTANCE = plugin_class() + +class WitWorldImpl: + def init_plugin(self) -> None: + pass + + def on_load(self, ctx: context.Context) -> None: + if _PLUGIN_INSTANCE: + try: + # In WIT, on-load returns result<_, string> + # componentize-py handles this by expecting None or raising Err + _PLUGIN_INSTANCE.on_load(ctx) + except Exception as e: + raise Err(str(e)) + + def on_unload(self, ctx: context.Context) -> None: + if _PLUGIN_INSTANCE: + try: + _PLUGIN_INSTANCE.on_unload(ctx) + except Exception as e: + raise Err(str(e)) + + def handle_event(self, event_id: int, srv: server.Server, evt: event.Event) -> event.Event: + if event_id in _EVENT_HANDLERS: + # Pass the inner event data to the handler + result = _EVENT_HANDLERS[event_id](srv, evt.value) + # If the handler returns something, assume it's the updated event data + if result is not None: + evt.value = result + return evt + + def handle_command(self, command_id: int, sender: command.CommandSender, srv: server.Server, args: command.ConsumedArgs) -> int: + if command_id in _COMMAND_HANDLERS: + try: + return _COMMAND_HANDLERS[command_id](sender, srv, args) + except Exception as e: + if hasattr(e, 'value') and isinstance(e.value, (command.CommandError_InvalidConsumption, command.CommandError_InvalidRequirement, command.CommandError_PermissionDenied, command.CommandError_CommandFailed)): + raise e + raise Err(command.CommandError_CommandFailed(text.TextComponent_text(str(e)))) + + raise Err(command.CommandError_CommandFailed(text.TextComponent_text(f"no handler registered for command id {command_id}"))) + + def handle_task(self, handler_id: int, srv: server.Server) -> None: + if handler_id in _TASK_HANDLERS: + _TASK_HANDLERS[handler_id](srv) + +class MetadataImpl: + def get_metadata(self) -> metadata.PluginMetadata: + if _PLUGIN_INSTANCE: + return _PLUGIN_INSTANCE.metadata() + return PluginMetadata("unknown", "0.0.0", [], "No metadata") diff --git a/src/pumpkin_api/app.py b/src/pumpkin_api/app.py new file mode 100644 index 0000000..1807c8d --- /dev/null +++ b/src/pumpkin_api/app.py @@ -0,0 +1,8 @@ +from . import WitWorldImpl, MetadataImpl +from .wit import wit_world + +class WitWorld(WitWorldImpl, wit_world.WitWorld): + pass + +class Metadata(MetadataImpl, wit_world.exports.Metadata): + pass diff --git a/src/pumpkin_api/build.py b/src/pumpkin_api/build.py new file mode 100644 index 0000000..6097036 --- /dev/null +++ b/src/pumpkin_api/build.py @@ -0,0 +1,35 @@ +import argparse +import subprocess +import sys +import os + +def main(): + parser = argparse.ArgumentParser(description="Build a Pumpkin Python plugin") + parser.add_argument("app_module", help="The python module containing the plugin (e.g. 'main')") + parser.add_argument("-o", "--output", default="plugin.wasm", help="Output wasm file name") + args = parser.parse_args() + + import pumpkin_api + pkg_dir = os.path.dirname(pumpkin_api.__file__) + wit_dir = os.path.join(pkg_dir, "wit_files") + + # Pass the parent directory of pumpkin_api to componentize-py so it can resolve `pumpkin_api` module + # in case it's not installed in a standard site-packages (e.g. editable install or no venv) + pkg_parent_dir = os.path.dirname(pkg_dir) + + cmd = [ + "componentize-py", + "-d", wit_dir, + "-w", "plugin", + "componentize", args.app_module, + "-o", args.output, + "-p", ".", + "-p", pkg_parent_dir + ] + + print(f"Running: {' '.join(cmd)}") + result = subprocess.run(cmd) + sys.exit(result.returncode) + +if __name__ == "__main__": + main() diff --git a/src/pumpkin_api/wit/componentize_py_async_support/__init__.py b/src/pumpkin_api/wit/componentize_py_async_support/__init__.py new file mode 100644 index 0000000..a7bd016 --- /dev/null +++ b/src/pumpkin_api/wit/componentize_py_async_support/__init__.py @@ -0,0 +1,529 @@ +import asyncio +import componentize_py_runtime +import subprocess + +from os import PathLike +from socket import AddressFamily, AddressInfo, SocketKind, socket +from concurrent.futures import Executor +from componentize_py_types import Result, Ok, Err +from dataclasses import dataclass +from typing import Any, Optional, TypeVar, TypeVarTuple, Callable, IO, Literal, cast +from contextvars import ContextVar, Context +from collections.abc import Coroutine, Awaitable, Sequence +from asyncio.protocols import BaseProtocol +from asyncio.transports import Transport, WriteTransport, DatagramTransport, SubprocessTransport, ReadTransport +from asyncio.base_events import Server + +try: + from ssl import SSLContext +except: + pass + +@dataclass +class _FutureState: + waitable_set: int | None + futures: dict[int, Any] + handles: list[asyncio.Handle] + pending_count: int + +class _ReturnCode: + COMPLETED = 0 + DROPPED = 1 + CANCELLED = 2 + +class _CallbackCode: + EXIT = 0 + YIELD = 1 + WAIT = 2 + POLL = 3 + +class _Event: + NONE = 0 + SUBTASK = 1 + STREAM_READ = 2 + STREAM_WRITE = 3 + FUTURE_READ = 4 + FUTURE_WRITE = 5 + CANCELLED = 6 + +class _Status: + STARTING = 0 + STARTED = 1 + RETURNED = 2 + START_CANCELLED = 3 + RETURN_CANCELLED = 4 + +_T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") +_ProtocolT = TypeVar("_ProtocolT", bound=BaseProtocol) + +async def _noop() -> None: + pass + +class _Loop(asyncio.AbstractEventLoop): + def __init__(self) -> None: + self.running: bool = False + self.exception: Optional[Any] = None + + def poll(self, future_state: _FutureState) -> None: + while True: + handles = future_state.handles + future_state.handles = [] + for handle in handles: + if not handle._cancelled: + handle._run() + + if self.exception is not None: + raise self.exception + + if len(handles) == 0 and len(future_state.handles) == 0: + return + + def get_debug(self) -> bool: + return False + + def is_running(self) -> bool: + return self.running + + def is_closed(self) -> bool: + return not self.running + + def stop(self) -> None: + self.running = False + + def close(self) -> None: + self.running = False + + def shutdown_asyncgens(self) -> Coroutine[Any, Any, None]: + return _noop() + + def call_exception_handler(self, context: dict[str, Any]) -> None: + self.exception = context.get('exception', None) + + def call_soon(self, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.Handle: + global _future_state + + if context is not None: + future_state = context[_future_state] + handle = asyncio.Handle(callback, args, self, context) + future_state.handles.append(handle) + return handle + else: + raise AssertionError + + def create_task(self, coroutine: Coroutine[Any, Any, _T], name: str | None = None, context: Context | None = None) -> asyncio.Task[_T]: + return asyncio.Task(coroutine, loop=self, context=context) + + def create_future(self) -> asyncio.Future[Any]: + return asyncio.Future(loop=self) + + # The remaining methods should be irrelevant for our purposes and thus unimplemented + + def run_until_complete(self, future: Awaitable[_T]) -> _T: + raise NotImplementedError + + def run_forever(self) -> None: + raise NotImplementedError + + async def shutdown_default_executor(self) -> None: + raise NotImplementedError + + def call_later(self, + delay: float, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.TimerHandle: + raise NotImplementedError + + def call_at(self, + when: float, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.TimerHandle: + raise NotImplementedError + + def time(self) -> float: + raise NotImplementedError + + def call_soon_threadsafe(self, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.Handle: + raise NotImplementedError + + def run_in_executor(self, + executor: Executor | None, + func: Callable[[*_Ts], _T], + *args: *_Ts) -> asyncio.Future[_T]: + raise NotImplementedError + + def set_default_executor(self, executor: Executor) -> None: + raise NotImplementedError + + async def getaddrinfo( + self, + host: bytes | str | None, + port: bytes | str | int | None, + *, + family: int = 0, + type: int = 0, + proto: int = 0, + flags: int = 0, + ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]: + raise NotImplementedError + + async def getnameinfo(self, + sockaddr: tuple[str, int] | tuple[str, int, int, int], + flags: int = 0) -> tuple[str, str]: + raise NotImplementedError + + async def create_connection( + self, + protocol_factory: Callable[[], _ProtocolT], + host: str | None = ..., + port: int | None = ..., + *, + ssl: bool | SSLContext | None = None, + family: int = 0, + proto: int = 0, + flags: int = 0, + sock: socket | None = None, + local_addr: tuple[str, int] | None = None, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + happy_eyeballs_delay: float | None = None, + interleave: int | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_server( + self, + protocol_factory: Callable[[], BaseProtocol], + host: str | Sequence[str] | None = None, + port: int | None = None, + *, + family: int = AddressFamily.AF_UNSPEC, + flags: int = AddressInfo.AI_PASSIVE, + sock: socket | None = ..., + backlog: int = 100, + ssl: bool | SSLContext | None = None, + reuse_address: bool | None = None, + reuse_port: bool | None = None, + keep_alive: bool | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + start_serving: bool = True, + ) -> Server: + raise NotImplementedError + + async def sendfile(self, + transport: WriteTransport, + file: IO[bytes], + offset: int = 0, + count: int | None = None, + *, + fallback: bool = True + ) -> int: + raise NotImplementedError + + async def start_tls( + self, + transport: WriteTransport, + protocol: BaseProtocol, + sslcontext: SSLContext, + *, + server_side: bool = False, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> Transport | None: + raise NotImplementedError + + async def create_unix_connection( + self, + protocol_factory: Callable[[], _ProtocolT], + path: str | None = None, + *, + ssl: bool | SSLContext | None = None, + sock: socket | None = None, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_unix_server( + self, + protocol_factory: Callable[[], BaseProtocol], + path: str | PathLike[str] | None = None, + *, + sock: socket | None = None, + backlog: int = 100, + ssl: bool | SSLContext | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + start_serving: bool = True, + ) -> Server: + raise NotImplementedError + + async def connect_accepted_socket( + self, + protocol_factory: Callable[[], _ProtocolT], + sock: socket, + *, + ssl: bool | SSLContext | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_datagram_endpoint( + self, + protocol_factory: Callable[[], _ProtocolT], + local_addr: tuple[str, int] | str | None = None, + remote_addr: tuple[str, int] | str | None = None, + *, + family: int = 0, + proto: int = 0, + flags: int = 0, + reuse_address: bool | None = None, + reuse_port: bool | None = None, + allow_broadcast: bool | None = None, + sock: socket | None = None, + ) -> tuple[DatagramTransport, _ProtocolT]: + raise NotImplementedError + + async def connect_read_pipe(self, + protocol_factory: Callable[[], _ProtocolT], + pipe: Any) -> tuple[ReadTransport, _ProtocolT]: + raise NotImplementedError + + async def connect_write_pipe(self, + protocol_factory: Callable[[], _ProtocolT], + pipe: Any) -> tuple[WriteTransport, _ProtocolT]: + raise NotImplementedError + + async def subprocess_shell( + self, + protocol_factory: Callable[[], _ProtocolT], + cmd: bytes | str, + *, + stdin: int | IO[Any] | None = -1, + stdout: int | IO[Any] | None = -1, + stderr: int | IO[Any] | None = -1, + universal_newlines: Literal[False] = False, + shell: Literal[True] = True, + bufsize: Literal[0] = 0, + encoding: None = None, + errors: None = None, + text: Literal[False] | None = None, + **kwargs: Any, + ) -> tuple[SubprocessTransport, _ProtocolT]: + raise NotImplementedError + + async def subprocess_exec( + self, + protocol_factory: Callable[[], _ProtocolT], + program: Any, + *args: Any, + stdin: int | IO[Any] | None = -1, + stdout: int | IO[Any] | None = -1, + stderr: int | IO[Any] | None = -1, + universal_newlines: Literal[False] = False, + shell: Literal[False] = False, + bufsize: Literal[0] = 0, + encoding: None = None, + errors: None = None, + **kwargs: Any, + ) -> tuple[SubprocessTransport, _ProtocolT]: + raise NotImplementedError + + def add_reader(self, fd: Any, callback: Callable[[*_Ts], Any], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_reader(self, fd: Any) -> bool: + raise NotImplementedError + + def add_writer(self, fd: Any, callback: Callable[[*_Ts], Any], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_writer(self, fd: Any) -> bool: + raise NotImplementedError + + async def sock_recv(self, sock: socket, nbytes: int) -> bytes: + raise NotImplementedError + + async def sock_recv_into(self, sock: socket, buf: Any) -> int: + raise NotImplementedError + + async def sock_recvfrom(self, sock: socket, bufsize: int) -> tuple[bytes, Any]: + raise NotImplementedError + + async def sock_recvfrom_into(self, sock: socket, buf: Any, nbytes: int = 0) -> tuple[int, Any]: + raise NotImplementedError + + async def sock_sendall(self, sock: socket, data: Any) -> None: + raise NotImplementedError + + async def sock_sendto(self, sock: socket, data: Any, address: Any) -> int: + raise NotImplementedError + + async def sock_connect(self, sock: socket, address: Any) -> None: + raise NotImplementedError + + async def sock_accept(self, sock: socket) -> tuple[socket, Any]: + raise NotImplementedError + + async def sock_sendfile( + self, + sock: socket, + file: IO[bytes], + offset: int = 0, + count: int | None = None, + *, + fallback: bool | None = None + ) -> int: + raise NotImplementedError + + def add_signal_handler(self, sig: int, callback: Callable[[*_Ts], object], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_signal_handler(self, sig: int) -> bool: + raise NotImplementedError + + def set_task_factory(self, factory: Any | None) -> None: + raise NotImplementedError + + def get_task_factory(self) -> None: + raise NotImplementedError + + def get_exception_handler(self) -> None: + raise NotImplementedError + + def set_exception_handler(self, handler: Any | None) -> None: + raise NotImplementedError + + def default_exception_handler(self, context: dict[str, Any]) -> None: + raise NotImplementedError + + def set_debug(self, enabled: bool) -> None: + raise NotImplementedError + +_future_state: ContextVar[_FutureState] = ContextVar("_future_state") +_loop = _Loop() +asyncio.set_event_loop(_loop) +_loop.running = True +asyncio.events._set_running_loop(_loop) + +def _set_future_state(future_state: _FutureState) -> None: + global _future_state + + _future_state.set(future_state) + +async def _return_result(export_index: int, borrows: int, coroutine: Any) -> None: + global _future_state + + try: + try: + result: Result[Any, Any] = Ok(await coroutine) + except Err as e: + result = e + + componentize_py_runtime.call_task_return(export_index, borrows, result) + except Exception as e: + _loop.exception = e + + assert _future_state.get().pending_count > 0 + _future_state.get().pending_count -= 1 + +def first_poll(export_index: int, borrows: int, coroutine: Any) -> int: + context = Context() + future_state = _FutureState(None, {}, [], 1) + context.run(_set_future_state, future_state) + asyncio.create_task(_return_result(export_index, borrows, coroutine), context=context) + return _poll(future_state) + +def _poll(future_state: _FutureState) -> int: + global _loop + + _loop.poll(future_state) + + if future_state.pending_count == 0: + if future_state.waitable_set is not None: + componentize_py_runtime.waitable_set_drop(future_state.waitable_set) + + return _CallbackCode.EXIT + else: + waitable_set = future_state.waitable_set + assert waitable_set is not None + componentize_py_runtime.context_set(future_state) + return _CallbackCode.WAIT | (waitable_set << 4) + +def callback(event0: int, event1: int, event2: int) -> int: + future_state = componentize_py_runtime.context_get() + componentize_py_runtime.context_set(None) + + match event0: + case _Event.NONE: + pass + case _Event.SUBTASK: + match event2: + case _Status.STARTING: + raise AssertionError + case _Status.STARTED: + pass + case _Status.RETURNED: + componentize_py_runtime.waitable_join(event1, 0) + componentize_py_runtime.subtask_drop(event1) + future_state.futures.pop(event1).set_result(event2) + case _: + # todo + raise NotImplementedError + case _Event.STREAM_READ | _Event.STREAM_WRITE | _Event.FUTURE_READ | _Event.FUTURE_WRITE: + componentize_py_runtime.waitable_join(event1, 0) + future_state.futures.pop(event1).set_result(event2) + case _: + # todo + raise NotImplementedError + + return _poll(future_state) + +async def await_result[T](result: Result[T, tuple[int, int]]) -> T: + global _loop + global _future_state + + if isinstance(result, Ok): + return result.value + else: + future_state = _future_state.get() + waitable, promise = result.value + future = _loop.create_future() + future_state.futures[waitable] = future + + if future_state.waitable_set is None: + future_state.waitable_set = componentize_py_runtime.waitable_set_new() + componentize_py_runtime.waitable_join(waitable, future_state.waitable_set) + + return cast(T, componentize_py_runtime.promise_get_result(await future, promise)) + +async def _wrap_spawned(coroutine: Any) -> None: + global _future_state + + try: + await coroutine + except Exception as e: + _loop.exception = e + + assert _future_state.get().pending_count > 0 + _future_state.get().pending_count -= 1 + +def spawn(coroutine: Any) -> None: + global _future_state + + _future_state.get().pending_count += 1 + + asyncio.create_task(_wrap_spawned(coroutine)) diff --git a/src/pumpkin_api/wit/componentize_py_async_support/futures.py b/src/pumpkin_api/wit/componentize_py_async_support/futures.py new file mode 100644 index 0000000..a50c16e --- /dev/null +++ b/src/pumpkin_api/wit/componentize_py_async_support/futures.py @@ -0,0 +1,93 @@ +import componentize_py_runtime +import componentize_py_async_support +import weakref + +from typing import TypeVar, Generic, cast, Self, Any, Callable +from types import TracebackType +from componentize_py_async_support import _ReturnCode + +T = TypeVar('T') + +class FutureReader(Generic[T]): + def __init__(self, type_: int, handle: int): + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.future_drop_readable, type_, handle) + + async def read(self) -> T: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + result = await componentize_py_async_support.await_result( + componentize_py_runtime.future_read(self.type_, handle) + ) + componentize_py_runtime.future_drop_readable(self.type_, handle) + return cast(T, result) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + if self.handle is not None: + self.finalizer.detach() + handle = self.handle + self.handle = None + componentize_py_runtime.future_drop_readable(self.type_, handle) + + return None + +async def write(type_: int, handle: int, value: Any) -> None: + await componentize_py_async_support.await_result( + componentize_py_runtime.future_write(type_, handle, value) + ) + componentize_py_runtime.future_drop_writable(type_, handle) + +def write_default(type_: int, handle: int, default: Callable[[], Any]) -> None: + componentize_py_async_support.spawn(write(type_, handle, default())) + +class FutureWriter(Generic[T]): + def __init__(self, type_: int, handle: int, default: Callable[[], T]): + self.type_ = type_ + self.handle: int | None = handle + self.default = default + self.finalizer = weakref.finalize(self, write_default, type_, handle, default) + + async def write(self, value: T) -> bool: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + code, _ = await componentize_py_async_support.await_result( + componentize_py_runtime.future_write(self.type_, handle, value) + ) + componentize_py_runtime.future_drop_writable(self.type_, handle) + match code: + case _ReturnCode.COMPLETED: + return True + case _ReturnCode.DROPPED: + return False + case _ReturnCode.CANCELLED: + # todo + raise NotImplementedError + case _: + raise AssertionError + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + if self.handle is not None: + componentize_py_async_support.spawn(self.write(self.default())) + + return None diff --git a/src/pumpkin_api/wit/componentize_py_async_support/streams.py b/src/pumpkin_api/wit/componentize_py_async_support/streams.py new file mode 100644 index 0000000..7650d62 --- /dev/null +++ b/src/pumpkin_api/wit/componentize_py_async_support/streams.py @@ -0,0 +1,189 @@ +import componentize_py_runtime +import componentize_py_async_support +import weakref + +from typing import TypeVar, Generic, Self, cast +from types import TracebackType +from componentize_py_async_support import _ReturnCode + +class ByteStreamReader: + def __init__(self, type_: int, handle: int): + self.writer_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) + + async def read(self, max_count: int) -> bytes: + if self.writer_dropped: + return bytes() + + code, values = await self._read(max_count) + + if code == _ReturnCode.DROPPED: + self.writer_dropped = True + + return values + + async def _read(self, max_count: int) -> tuple[int, bytes]: + if self.handle is not None: + return cast(tuple[int, bytes], await componentize_py_async_support.await_result( + componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + )) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_readable(self.type_, handle) + return None + +class ByteStreamWriter: + def __init__(self, type_: int, handle: int): + self.reader_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) + + async def write(self, source: bytes) -> int: + if self.reader_dropped: + return 0 + + code, count = await self._write(source) + + if code == _ReturnCode.DROPPED: + self.reader_dropped = True + + return count + + async def _write(self, source: bytes) -> tuple[int, int]: + if self.handle is not None: + return await componentize_py_async_support.await_result( + componentize_py_runtime.stream_write(self.type_, self.handle, source) + ) + else: + raise AssertionError + + async def write_all(self, source: bytes) -> int: + total = 0 + + while len(source) > 0 and not self.reader_dropped: + count = await self.write(source) + source = source[count:] + total += count + + return total + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_writable(self.type_, handle) + return None + +T = TypeVar('T') + +class StreamReader(Generic[T]): + def __init__(self, type_: int, handle: int): + self.writer_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) + + async def read(self, max_count: int) -> list[T]: + if self.writer_dropped: + return [] + + code, values = await self._read(max_count) + + if code == _ReturnCode.DROPPED: + self.writer_dropped = True + + return values + + async def _read(self, max_count: int) -> tuple[int, list[T]]: + if self.handle is not None: + return cast(tuple[int, list[T]], await componentize_py_async_support.await_result( + componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + )) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_readable(self.type_, handle) + return None + +class StreamWriter(Generic[T]): + def __init__(self, type_: int, handle: int): + self.reader_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) + + async def write(self, source: list[T]) -> int: + if self.reader_dropped: + return 0 + + code, count = await self._write(source) + + if code == _ReturnCode.DROPPED: + self.reader_dropped = True + + return count + + async def _write(self, source: list[T]) -> tuple[int, int]: + if self.handle is not None: + return await componentize_py_async_support.await_result( + componentize_py_runtime.stream_write(self.type_, self.handle, source) + ) + else: + raise AssertionError + + async def write_all(self, source: list[T]) -> int: + total = 0 + + while len(source) > 0 and not self.reader_dropped: + count = await self.write(source) + source = source[count:] + total += count + + return total + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_writable(self.type_, handle) + return None diff --git a/src/pumpkin_api/wit/componentize_py_runtime.pyi b/src/pumpkin_api/wit/componentize_py_runtime.pyi new file mode 100644 index 0000000..ff09cf8 --- /dev/null +++ b/src/pumpkin_api/wit/componentize_py_runtime.pyi @@ -0,0 +1,34 @@ +from typing import Any +from componentize_py_types import Result + +def call_task_return(index: int, borrows: int, result: Any) -> None: ... + +def waitable_set_drop(set: int) -> None: ... + +def context_set(value: Any) -> None: ... + +def context_get() -> Any: ... + +def waitable_join(waitable: int, set: int) -> None: ... + +def subtask_drop(task: int) -> None: ... + +def waitable_set_new() -> int: ... + +def promise_get_result(event: int, promise: int) -> Any: ... + +def future_read(ty: int, future: int) -> Result[Any, tuple[int, int]]: ... + +def future_write(ty: int, future: int, value: Any) -> Result[tuple[int, int], tuple[int, int]]: ... + +def future_drop_readable(ty: int, future: int) -> None: ... + +def future_drop_writable(ty: int, future: int) -> None: ... + +def stream_read(ty: int, stream: int, max_count: int) -> Result[tuple[int, bytes | list[Any]], tuple[int, int]]: ... + +def stream_write(ty: int, stream: int, values: bytes | list[Any]) -> Result[tuple[int, int], tuple[int, int]]: ... + +def stream_drop_readable(ty: int, stream: int) -> None: ... + +def stream_drop_writable(ty: int, stream: int) -> None: ... diff --git a/src/pumpkin_api/wit/componentize_py_types.py b/src/pumpkin_api/wit/componentize_py_types.py new file mode 100644 index 0000000..9867d14 --- /dev/null +++ b/src/pumpkin_api/wit/componentize_py_types.py @@ -0,0 +1,19 @@ +from typing import TypeVar, Generic, Union +from dataclasses import dataclass + +S = TypeVar('S') +@dataclass +class Some(Generic[S]): + value: S + +T = TypeVar('T') +@dataclass +class Ok(Generic[T]): + value: T + +E = TypeVar('E') +@dataclass(frozen=True) +class Err(Generic[E], Exception): + value: E + +Result = Union[Ok[T], Err[E]] diff --git a/src/pumpkin_api/wit/poll_loop.py b/src/pumpkin_api/wit/poll_loop.py new file mode 100644 index 0000000..504c958 --- /dev/null +++ b/src/pumpkin_api/wit/poll_loop.py @@ -0,0 +1,440 @@ +"""Defines a custom `asyncio` event loop backed by `wasi:io/poll#poll`. + +This also includes helper classes and functions for working with `wasi:http`. + +This is only useful for `wasi:http@0.2.x`; `wasi:http@0.3.x` uses a different +mechanism to model concurrency. +""" + +import asyncio +import socket +import subprocess + +from componentize_py_types import Ok, Err +from wit_world.imports import types, streams, poll, outgoing_handler +from wit_world.imports.types import ( + IncomingBody, + OutgoingBody, + OutgoingRequest, + IncomingResponse, +) +from wit_world.imports.streams import StreamError_Closed, InputStream +from wit_world.imports.poll import Pollable +from typing import Optional, cast + +# Maximum number of bytes to read at a time +READ_SIZE: int = 16 * 1024 + + +async def send(request: OutgoingRequest) -> IncomingResponse: + """Send the specified request and wait asynchronously for the response.""" + + future = outgoing_handler.handle(request, None) + + while True: + response = future.get() + if response is None: + await register(cast(PollLoop, asyncio.get_event_loop()), future.subscribe()) + else: + if isinstance(response, Ok): + if isinstance(response.value, Ok): + return response.value.value + else: + raise response.value + else: + raise response + + +class Stream: + """Reader abstraction over `wasi:http/types#incoming-body`.""" + + def __init__(self, body: IncomingBody): + self.body: Optional[IncomingBody] = body + self.stream: Optional[InputStream] = body.stream() + + async def next(self) -> Optional[bytes]: + """Wait for the next chunk of data to arrive on the stream. + + This will return `None` when the end of the stream has been reached. + """ + while True: + try: + if self.stream is None: + return None + else: + buffer = self.stream.read(READ_SIZE) + if len(buffer) == 0: + await register( + cast(PollLoop, asyncio.get_event_loop()), + self.stream.subscribe(), + ) + else: + return buffer + except Err as e: + if isinstance(e.value, StreamError_Closed): + if self.stream is not None: + self.stream.__exit__(None, None, None) + self.stream = None + if self.body is not None: + IncomingBody.finish(self.body) + self.body = None + else: + raise e + + +class Sink: + """Writer abstraction over `wasi:http/types#outgoing-body`.""" + + def __init__(self, body: OutgoingBody): + self.body = body + self.stream = body.write() + + async def send(self, chunk: bytes): + """Write the specified bytes to the sink. + + This may need to yield according to the backpressure requirements of the sink. + """ + offset = 0 + flushing = False + while True: + count = self.stream.check_write() + if count == 0: + await register( + cast(PollLoop, asyncio.get_event_loop()), self.stream.subscribe() + ) + elif offset == len(chunk): + if flushing: + return + else: + self.stream.flush() + flushing = True + else: + count = min(count, len(chunk) - offset) + self.stream.write(chunk[offset : offset + count]) + offset += count + + def close(self): + """Close the stream, indicating no further data will be written.""" + + self.stream.__exit__(None, None, None) + self.stream = None + OutgoingBody.finish(self.body, None) + self.body = None + + +class PollLoop(asyncio.AbstractEventLoop): + """Custom `asyncio` event loop backed by `wasi:io/poll#poll`.""" + + def __init__(self): + self.wakers = [] + self.running = False + self.handles = [] + self.exception = None + + def get_debug(self): + return False + + def run_until_complete(self, future): + future = asyncio.ensure_future(future, loop=self) + + self.running = True + asyncio.events._set_running_loop(self) + while self.running and not future.done(): + handles = self.handles + self.handles = [] + for handle in handles: + if not handle._cancelled: + handle._run() + + if self.wakers: + [pollables, wakers] = list(map(list, zip(*self.wakers))) + + new_wakers = [] + ready = [False] * len(pollables) + for index in poll.poll(pollables): + ready[index] = True + + for (ready, pollable), waker in zip(zip(ready, pollables), wakers): + if ready: + pollable.__exit__(None, None, None) + waker.set_result(None) + else: + new_wakers.append((pollable, waker)) + + self.wakers = new_wakers + + if self.exception is not None: + raise self.exception + + return future.result() + + def is_running(self): + return self.running + + def is_closed(self): + return not self.running + + def stop(self): + self.running = False + + def close(self): + self.running = False + + def shutdown_asyncgens(self): + pass + + def call_exception_handler(self, context): + self.exception = context.get("exception", None) + + def call_soon(self, callback, *args, context=None): + handle = asyncio.Handle(callback, args, self, context) + self.handles.append(handle) + return handle + + def create_task(self, coroutine): + return asyncio.Task(coroutine, loop=self) + + def create_future(self): + return asyncio.Future(loop=self) + + # The remaining methods should be irrelevant for our purposes and thus unimplemented + + def run_forever(self): + raise NotImplementedError + + async def shutdown_default_executor(self): + raise NotImplementedError + + def _timer_handle_cancelled(self, handle): + raise NotImplementedError + + def call_later(self, delay, callback, *args, context=None): + raise NotImplementedError + + def call_at(self, when, callback, *args, context=None): + raise NotImplementedError + + def time(self): + raise NotImplementedError + + def call_soon_threadsafe(self, callback, *args, context=None): + raise NotImplementedError + + def run_in_executor(self, executor, func, *args): + raise NotImplementedError + + def set_default_executor(self, executor): + raise NotImplementedError + + async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): + raise NotImplementedError + + async def getnameinfo(self, sockaddr, flags=0): + raise NotImplementedError + + async def create_connection( + self, + protocol_factory, + host=None, + port=None, + *, + ssl=None, + family=0, + proto=0, + flags=0, + sock=None, + local_addr=None, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + happy_eyeballs_delay=None, + interleave=None, + ): + raise NotImplementedError + + async def create_server( + self, + protocol_factory, + host=None, + port=None, + *, + family=socket.AF_UNSPEC, + flags=socket.AI_PASSIVE, + sock=None, + backlog=100, + ssl=None, + reuse_address=None, + reuse_port=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + start_serving=True, + ): + raise NotImplementedError + + async def sendfile(self, transport, file, offset=0, count=None, *, fallback=True): + raise NotImplementedError + + async def start_tls( + self, + transport, + protocol, + sslcontext, + *, + server_side=False, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_unix_connection( + self, + protocol_factory, + path=None, + *, + ssl=None, + sock=None, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_unix_server( + self, + protocol_factory, + path=None, + *, + sock=None, + backlog=100, + ssl=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + start_serving=True, + ): + raise NotImplementedError + + async def connect_accepted_socket( + self, + protocol_factory, + sock, + *, + ssl=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_datagram_endpoint( + self, + protocol_factory, + local_addr=None, + remote_addr=None, + *, + family=0, + proto=0, + flags=0, + reuse_address=None, + reuse_port=None, + allow_broadcast=None, + sock=None, + ): + raise NotImplementedError + + async def connect_read_pipe(self, protocol_factory, pipe): + raise NotImplementedError + + async def connect_write_pipe(self, protocol_factory, pipe): + raise NotImplementedError + + async def subprocess_shell( + self, + protocol_factory, + cmd, + *, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ): + raise NotImplementedError + + async def subprocess_exec( + self, + protocol_factory, + *args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ): + raise NotImplementedError + + def add_reader(self, fd, callback, *args): + raise NotImplementedError + + def remove_reader(self, fd): + raise NotImplementedError + + def add_writer(self, fd, callback, *args): + raise NotImplementedError + + def remove_writer(self, fd): + raise NotImplementedError + + async def sock_recv(self, sock, nbytes): + raise NotImplementedError + + async def sock_recv_into(self, sock, buf): + raise NotImplementedError + + async def sock_recvfrom(self, sock, bufsize): + raise NotImplementedError + + async def sock_recvfrom_into(self, sock, buf, nbytes=0): + raise NotImplementedError + + async def sock_sendall(self, sock, data): + raise NotImplementedError + + async def sock_sendto(self, sock, data, address): + raise NotImplementedError + + async def sock_connect(self, sock, address): + raise NotImplementedError + + async def sock_accept(self, sock): + raise NotImplementedError + + async def sock_sendfile(self, sock, file, offset=0, count=None, *, fallback=None): + raise NotImplementedError + + def add_signal_handler(self, sig, callback, *args): + raise NotImplementedError + + def remove_signal_handler(self, sig): + raise NotImplementedError + + def set_task_factory(self, factory): + raise NotImplementedError + + def get_task_factory(self): + raise NotImplementedError + + def get_exception_handler(self): + raise NotImplementedError + + def set_exception_handler(self, handler): + raise NotImplementedError + + def default_exception_handler(self, context): + raise NotImplementedError + + def set_debug(self, enabled): + raise NotImplementedError + + +async def register(loop: PollLoop, pollable: Pollable): + waker = loop.create_future() + loop.wakers.append((pollable, waker)) + await waker diff --git a/src/pumpkin_api/wit/wit_world/__init__.py b/src/pumpkin_api/wit/wit_world/__init__.py new file mode 100644 index 0000000..58938bf --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/__init__.py @@ -0,0 +1,70 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from .imports import event +from .imports import server +from .imports import command +from .imports import context + + +class WitWorld(Protocol): + + @abstractmethod + def init_plugin(self) -> None: + """ + --- Plugin-provided exports --- + Called by the host once to initialize the plugin. + Internal use only; plugins should use the `register_plugin!` macro. + """ + raise NotImplementedError + + @abstractmethod + def on_load(self, context: context.Context) -> None: + """ + Called when the plugin is being loaded. + Use this to register commands, events, and perform initial setup. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + + @abstractmethod + def on_unload(self, context: context.Context) -> None: + """ + Called when the plugin is being unloaded. + Use this to save data and clean up resources. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + + @abstractmethod + def handle_event(self, event_id: int, server: server.Server, event: event.Event) -> event.Event: + """ + Dispatches an event to the plugin for handling. + """ + raise NotImplementedError + + @abstractmethod + def handle_command(self, command_id: int, sender: command.CommandSender, server: server.Server, args: command.ConsumedArgs) -> int: + """ + Dispatches a command execution to the plugin. + + Raises: `componentize_py_types.Err(wit_world.imports.command.CommandError)` + """ + raise NotImplementedError + + @abstractmethod + def handle_task(self, handler_id: int, server: server.Server) -> None: + """ + Dispatches a scheduled task execution to the plugin. + """ + raise NotImplementedError + diff --git a/src/pumpkin_api/wit/wit_world/exports/__init__.py b/src/pumpkin_api/wit/wit_world/exports/__init__.py new file mode 100644 index 0000000..67d2941 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/exports/__init__.py @@ -0,0 +1,19 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..exports import metadata + +class Metadata(Protocol): + + @abstractmethod + def get_metadata(self) -> metadata.PluginMetadata: + raise NotImplementedError + + diff --git a/src/pumpkin_api/wit/wit_world/exports/metadata.py b/src/pumpkin_api/wit/wit_world/exports/metadata.py new file mode 100644 index 0000000..d2b4d4a --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/exports/metadata.py @@ -0,0 +1,22 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Plugin metadata describing the plugin and its compatibility. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +@dataclass +class PluginMetadata: + name: str + version: str + authors: List[str] + description: str + diff --git a/src/pumpkin_api/wit/wit_world/imports/__init__.py b/src/pumpkin_api/wit/wit_world/imports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pumpkin_api/wit/wit_world/imports/command.py b/src/pumpkin_api/wit/wit_world/imports/command.py new file mode 100644 index 0000000..c12bdc6 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/command.py @@ -0,0 +1,539 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import entity +from ..imports import text +from ..imports import world +from ..imports import player +from ..imports import server + +class Gamemode(Enum): + SURVIVAL = 0 + CREATIVE = 1 + ADVENTURE = 2 + SPECTATOR = 3 + +class BossbarColor(Enum): + PINK = 0 + BLUE = 1 + RED = 2 + GREEN = 3 + YELLOW = 4 + PURPLE = 5 + WHITE = 6 + +class BossbarStyle(Enum): + NO_DIVISION = 0 + NOTCHES6 = 1 + NOTCHES10 = 2 + NOTCHES12 = 3 + NOTCHES20 = 4 + +class SoundCategory(Enum): + MASTER = 0 + MUSIC = 1 + RECORDS = 2 + WEATHER = 3 + BLOCKS = 4 + HOSTILE = 5 + NEUTRAL = 6 + PLAYERS = 7 + AMBIENT = 8 + VOICE = 9 + UI = 10 + +class EntityAnchor(Enum): + FEET = 0 + EYES = 1 + + +@dataclass +class Number_Float64: + value: float + + +@dataclass +class Number_Float32: + value: float + + +@dataclass +class Number_Int32: + value: int + + +@dataclass +class Number_Int64: + value: int + + +Number = Union[Number_Float64, Number_Float32, Number_Int32, Number_Int64] + + + +@dataclass +class NotInBounds_LowerBound: + value: Tuple[Number, Number] + + +@dataclass +class NotInBounds_UpperBound: + value: Tuple[Number, Number] + + +NotInBounds = Union[NotInBounds_LowerBound, NotInBounds_UpperBound] + + + +@dataclass +class Arg_Entities: + value: List[entity.Entity] + + +@dataclass +class Arg_Entity: + value: entity.Entity + + +@dataclass +class Arg_Players: + value: List[player.Player] + + +@dataclass +class Arg_BlockPos: + value: Tuple[int, int, int] + + +@dataclass +class Arg_Pos3d: + value: Tuple[float, float, float] + + +@dataclass +class Arg_Pos2d: + value: Tuple[float, float] + + +@dataclass +class Arg_Rotation: + value: Tuple[float, bool, float, bool] + + +@dataclass +class Arg_GameMode: + value: Gamemode + + +@dataclass +class Arg_Difficulty: + value: server.Difficulty + + +@dataclass +class Arg_Item: + value: str + + +@dataclass +class Arg_ItemPredicate: + value: str + + +@dataclass +class Arg_ResourceLocation: + value: str + + +@dataclass +class Arg_Block: + value: str + + +@dataclass +class Arg_BlockPredicate: + value: str + + +@dataclass +class Arg_BossbarColor: + value: BossbarColor + + +@dataclass +class Arg_BossbarStyle: + value: BossbarStyle + + +@dataclass +class Arg_Particle: + value: str + + +@dataclass +class Arg_Msg: + value: str + + +@dataclass +class Arg_TextComponent: + value: text.TextComponent + + +@dataclass +class Arg_Time: + value: int + + +@dataclass +class Arg_Num: + value: Result[Number, NotInBounds] + + +@dataclass +class Arg_Bool: + value: bool + + +@dataclass +class Arg_Simple: + value: str + + +@dataclass +class Arg_SoundCategory: + value: SoundCategory + + +@dataclass +class Arg_DamageType: + value: str + + +@dataclass +class Arg_Effect: + value: str + + +@dataclass +class Arg_Enchantment: + value: str + + +@dataclass +class Arg_EntityAnchor: + value: EntityAnchor + + +Arg = Union[Arg_Entities, Arg_Entity, Arg_Players, Arg_BlockPos, Arg_Pos3d, Arg_Pos2d, Arg_Rotation, Arg_GameMode, Arg_Difficulty, Arg_Item, Arg_ItemPredicate, Arg_ResourceLocation, Arg_Block, Arg_BlockPredicate, Arg_BossbarColor, Arg_BossbarStyle, Arg_Particle, Arg_Msg, Arg_TextComponent, Arg_Time, Arg_Num, Arg_Bool, Arg_Simple, Arg_SoundCategory, Arg_DamageType, Arg_Effect, Arg_Enchantment, Arg_EntityAnchor] + + +class ConsumedArgs: + + def get_value(self, key: str) -> Arg: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + +@dataclass +class CommandSenderType_Rcon: + pass + + +@dataclass +class CommandSenderType_Console: + pass + + +@dataclass +class CommandSenderType_Player: + value: player.Player + + +@dataclass +class CommandSenderType_CommandBlock: + value: Tuple[entity.CommandBlockEntity, world.World] + + +@dataclass +class CommandSenderType_Dummy: + pass + + +CommandSenderType = Union[CommandSenderType_Rcon, CommandSenderType_Console, CommandSenderType_Player, CommandSenderType_CommandBlock, CommandSenderType_Dummy] + + +class PermissionLevel(Enum): + ZERO = 0 + ONE = 1 + TWO = 2 + THREE = 3 + FOUR = 4 + +class CommandSender: + + def get_command_sender_type(self) -> CommandSenderType: + raise NotImplementedError + def send_message(self, text: text.TextComponent) -> None: + raise NotImplementedError + def set_success_count(self, count: int) -> None: + raise NotImplementedError + def is_player(self) -> bool: + raise NotImplementedError + def is_console(self) -> bool: + raise NotImplementedError + def as_player(self) -> Optional[player.Player]: + raise NotImplementedError + def permission_level(self) -> PermissionLevel: + raise NotImplementedError + def has_permission_level(self, level: PermissionLevel) -> bool: + raise NotImplementedError + def has_permission(self, server: server.Server, node: str) -> bool: + raise NotImplementedError + def position(self) -> Optional[Tuple[float, float, float]]: + raise NotImplementedError + def world(self) -> Optional[world.World]: + raise NotImplementedError + def get_locale(self) -> common.Locale: + raise NotImplementedError + def should_receive_feedback(self) -> bool: + raise NotImplementedError + def should_broadcast_console_to_ops(self) -> bool: + raise NotImplementedError + def should_track_output(self) -> bool: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class StringType(Enum): + SINGLE_WORD = 0 + QUOTABLE = 1 + GREEDY = 2 + + +@dataclass +class ArgumentType_Bool: + pass + + +@dataclass +class ArgumentType_Float: + value: Tuple[Optional[float], Optional[float]] + + +@dataclass +class ArgumentType_Double: + value: Tuple[Optional[float], Optional[float]] + + +@dataclass +class ArgumentType_Integer: + value: Tuple[Optional[int], Optional[int]] + + +@dataclass +class ArgumentType_Long: + value: Tuple[Optional[int], Optional[int]] + + +@dataclass +class ArgumentType_String: + value: StringType + + +@dataclass +class ArgumentType_Entities: + pass + + +@dataclass +class ArgumentType_Entity: + pass + + +@dataclass +class ArgumentType_Players: + pass + + +@dataclass +class ArgumentType_GameProfile: + pass + + +@dataclass +class ArgumentType_BlockPos: + pass + + +@dataclass +class ArgumentType_Position3d: + pass + + +@dataclass +class ArgumentType_Position2d: + pass + + +@dataclass +class ArgumentType_BlockState: + pass + + +@dataclass +class ArgumentType_BlockPredicate: + pass + + +@dataclass +class ArgumentType_Item: + pass + + +@dataclass +class ArgumentType_ItemPredicate: + pass + + +@dataclass +class ArgumentType_Component: + pass + + +@dataclass +class ArgumentType_Rotation: + pass + + +@dataclass +class ArgumentType_ResourceLocation: + pass + + +@dataclass +class ArgumentType_EntityAnchor: + pass + + +@dataclass +class ArgumentType_Gamemode: + pass + + +@dataclass +class ArgumentType_Difficulty: + pass + + +@dataclass +class ArgumentType_Time: + value: int + + +@dataclass +class ArgumentType_Resource: + value: str + + +ArgumentType = Union[ArgumentType_Bool, ArgumentType_Float, ArgumentType_Double, ArgumentType_Integer, ArgumentType_Long, ArgumentType_String, ArgumentType_Entities, ArgumentType_Entity, ArgumentType_Players, ArgumentType_GameProfile, ArgumentType_BlockPos, ArgumentType_Position3d, ArgumentType_Position2d, ArgumentType_BlockState, ArgumentType_BlockPredicate, ArgumentType_Item, ArgumentType_ItemPredicate, ArgumentType_Component, ArgumentType_Rotation, ArgumentType_ResourceLocation, ArgumentType_EntityAnchor, ArgumentType_Gamemode, ArgumentType_Difficulty, ArgumentType_Time, ArgumentType_Resource] + + + +@dataclass +class CommandError_InvalidConsumption: + value: Optional[str] + + +@dataclass +class CommandError_InvalidRequirement: + pass + + +@dataclass +class CommandError_PermissionDenied: + pass + + +@dataclass +class CommandError_CommandFailed: + value: text.TextComponent + + +CommandError = Union[CommandError_InvalidConsumption, CommandError_InvalidRequirement, CommandError_PermissionDenied, CommandError_CommandFailed] + + +class CommandNode: + + @classmethod + def literal(cls, name: str) -> Self: + raise NotImplementedError + @classmethod + def argument(cls, name: str, arg_type: ArgumentType) -> Self: + raise NotImplementedError + def then(self, node: Self) -> None: + raise NotImplementedError + def execute_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def require_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class Command: + + def __init__(self, names: List[str], description: str) -> None: + """ + First name is primary, rest are aliases + """ + raise NotImplementedError + + def then(self, node: CommandNode) -> None: + raise NotImplementedError + def execute_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/common.py b/src/pumpkin_api/wit/wit_world/imports/common.py new file mode 100644 index 0000000..34f064e --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/common.py @@ -0,0 +1,213 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Provides common types and utilities used across the entire plugin API. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class Hand(Enum): + """ + Represents the hands of a player. + """ + LEFT = 0 + RIGHT = 1 + +class GameMode(Enum): + """ + Represents the game modes available in Minecraft. + """ + SURVIVAL = 0 + CREATIVE = 1 + ADVENTURE = 2 + SPECTATOR = 3 + +class NamedColor(Enum): + """ + The 16 base Minecraft colors. + """ + BLACK = 0 + DARK_BLUE = 1 + DARK_GREEN = 2 + DARK_AQUA = 3 + DARK_RED = 4 + DARK_PURPLE = 5 + GOLD = 6 + GRAY = 7 + DARK_GRAY = 8 + BLUE = 9 + GREEN = 10 + AQUA = 11 + RED = 12 + LIGHT_PURPLE = 13 + YELLOW = 14 + WHITE = 15 + +@dataclass +class RgbColor: + """ + An RGB color represented by its red, green, and blue components. + """ + r: int + g: int + b: int + +@dataclass +class ArgbColor: + """ + An ARGB color with alpha (transparency) channel. + """ + a: int + r: int + g: int + b: int + +@dataclass +class ItemStack: + """ + Represents a stack of items with its type and count. + """ + registry_key: str + count: int + +class Locale(Enum): + """ + All Minecraft-supported locales. + """ + AF_ZA = 0 + AR_SA = 1 + AST_ES = 2 + AZ_AZ = 3 + BA_RU = 4 + BAR = 5 + BE_BY = 6 + BG_BG = 7 + BR_FR = 8 + BRB = 9 + BS_BA = 10 + CA_ES = 11 + CS_CZ = 12 + CY_GB = 13 + DA_DK = 14 + DE_AT = 15 + DE_CH = 16 + DE_DE = 17 + EL_GR = 18 + EN_AU = 19 + EN_CA = 20 + EN_GB = 21 + EN_NZ = 22 + EN_PT = 23 + EN_UD = 24 + EN_US = 25 + ENP = 26 + ENWS = 27 + EO_UY = 28 + ES_AR = 29 + ES_CL = 30 + ES_EC = 31 + ES_ES = 32 + ES_MX = 33 + ES_UY = 34 + ES_VE = 35 + ESAN = 36 + ET_EE = 37 + EU_ES = 38 + FA_IR = 39 + FI_FI = 40 + FIL_PH = 41 + FO_FO = 42 + FR_CA = 43 + FR_FR = 44 + FRA_DE = 45 + FUR_IT = 46 + FY_NL = 47 + GA_IE = 48 + GD_GB = 49 + GL_ES = 50 + HAW_US = 51 + HE_IL = 52 + HI_IN = 53 + HR_HR = 54 + HU_HU = 55 + HY_AM = 56 + ID_ID = 57 + IG_NG = 58 + IO_EN = 59 + IS_IS = 60 + ISV = 61 + IT_IT = 62 + JA_JP = 63 + JBO_EN = 64 + KA_GE = 65 + KK_KZ = 66 + KN_IN = 67 + KO_KR = 68 + KSH = 69 + KW_GB = 70 + LA_LA = 71 + LB_LU = 72 + LI_LI = 73 + LMO = 74 + LO_LA = 75 + LOL_US = 76 + LT_LT = 77 + LV_LV = 78 + LZH = 79 + MK_MK = 80 + MN_MN = 81 + MS_MY = 82 + MT_MT = 83 + NAH = 84 + NDS_DE = 85 + NL_BE = 86 + NL_NL = 87 + NN_NO = 88 + NO_NO = 89 + OC_FR = 90 + OVD = 91 + PL_PL = 92 + PT_BR = 93 + PT_PT = 94 + QYA_AA = 95 + RO_RO = 96 + RPR = 97 + RU_RU = 98 + RY_UA = 99 + SAH_SAH = 100 + SE_NO = 101 + SK_SK = 102 + SL_SI = 103 + SO_SO = 104 + SQ_AL = 105 + SR_CS = 106 + SR_SP = 107 + SV_SE = 108 + SXU = 109 + SZL = 110 + TA_IN = 111 + TH_TH = 112 + TL_PH = 113 + TLH_AA = 114 + TOK = 115 + TR_TR = 116 + TT_RU = 117 + UK_UA = 118 + VAL_ES = 119 + VEC_IT = 120 + VI_VN = 121 + YI_DE = 122 + YO_NG = 123 + ZH_CN = 124 + ZH_HK = 125 + ZH_TW = 126 + ZLM_ARAB = 127 + + diff --git a/src/pumpkin_api/wit/wit_world/imports/context.py b/src/pumpkin_api/wit/wit_world/imports/context.py new file mode 100644 index 0000000..3007ef5 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/context.py @@ -0,0 +1,70 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +The core context provided to a plugin during initialization and event handling. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import permission +from ..imports import event +from ..imports import server +from ..imports import command + +class Context: + """ + A contextual handle for plugin-related operations. + """ + + def register_event(self, handler_id: int, event_type: event.EventType, event_priority: event.EventPriority, blocking: bool) -> None: + """ + Registers a handler for a specific event. + + * `handler-id`: Unique ID for the event handler. + * `event-type`: The type of event to listen for. + * `event-priority`: When this handler should be called relative to others. + * `blocking`: Whether the event should wait for this handler to finish. + """ + raise NotImplementedError + def register_command(self, command: command.Command, permission: str) -> None: + """ + Registers a new command. + + * `command`: The command definition. + * `permission`: The required permission node to execute this command. + """ + raise NotImplementedError + def register_permission(self, permission: permission.Permission) -> None: + """ + Registers a permission node with the server. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + def get_data_folder(self) -> str: + """ + Returns the path to the plugin's private data folder. + """ + raise NotImplementedError + def get_server(self) -> server.Server: + """ + Returns the global server instance. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/entity.py b/src/pumpkin_api/wit/wit_world/imports/entity.py new file mode 100644 index 0000000..6b61c37 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/entity.py @@ -0,0 +1,84 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class BlockEntity: + + def resource_location(self) -> str: + raise NotImplementedError + def get_position(self) -> Tuple[int, int, int]: + raise NotImplementedError + def get_id(self) -> int: + raise NotImplementedError + def is_dirty(self) -> bool: + """ + write-nbt: func(nbt: nbt-compound); + from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; + tick: func(world: simple-world); + write-internal: func(nbt: nbt-compound); + chunk-data-nbt: func() -> option option; + set-block-state: func(block-state: block-state-id); + on-block-replaced: func(world: simple-world, position: block-position); + """ + raise NotImplementedError + def clear_dirty(self) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class CommandBlockEntity: + + def get_block_entity(self) -> BlockEntity: + raise NotImplementedError + def last_output(self) -> str: + raise NotImplementedError + def track_output(self) -> bool: + raise NotImplementedError + def success_count(self) -> int: + raise NotImplementedError + def command(self) -> str: + raise NotImplementedError + def auto(self) -> bool: + raise NotImplementedError + def condition_met(self) -> bool: + raise NotImplementedError + def powered(self) -> bool: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + +@dataclass +class Entity_CommandBlockEntity: + value: CommandBlockEntity + + +Entity = Union[Entity_CommandBlockEntity] + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/event.py b/src/pumpkin_api/wit/wit_world/imports/event.py new file mode 100644 index 0000000..62ced7e --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/event.py @@ -0,0 +1,434 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text +from ..imports import world +from ..imports import player + +class EventPriority(Enum): + HIGHEST = 0 + HIGH = 1 + NORMAL = 2 + LOW = 3 + LOWEST = 4 + +@dataclass +class PlayerJoinEventData: + player: player.Player + join_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerLeaveEventData: + player: player.Player + leave_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerLoginEventData: + player: player.Player + kick_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerChatEventData: + player: player.Player + message: str + recipients: List[player.Player] + cancelled: bool + +@dataclass +class PlayerCommandSendEventData: + player: player.Player + command: str + cancelled: bool + +@dataclass +class PlayerPermissionCheckEventData: + player: player.Player + permission: str + permission_result: bool + +@dataclass +class PlayerMoveEventData: + player: player.Player + from_position: Tuple[float, float, float] + to_position: Tuple[float, float, float] + cancelled: bool + +@dataclass +class PlayerTeleportEventData: + player: player.Player + from_position: Tuple[float, float, float] + to_position: Tuple[float, float, float] + cancelled: bool + +@dataclass +class PlayerChangeWorldEventData: + player: player.Player + previous_world: world.World + new_world: world.World + position: Tuple[float, float, float] + yaw: float + pitch: float + cancelled: bool + +@dataclass +class PlayerExpChangeEventData: + player: player.Player + amount: int + +@dataclass +class PlayerItemHeldEventData: + player: player.Player + previous_slot: int + new_slot: int + cancelled: bool + +@dataclass +class PlayerChangedMainHandEventData: + player: player.Player + main_hand: common.Hand + +@dataclass +class PlayerGamemodeChangeEventData: + player: player.Player + previous_gamemode: common.GameMode + new_gamemode: common.GameMode + cancelled: bool + +@dataclass +class PlayerCustomPayloadEventData: + player: player.Player + channel: str + data: bytes + +class PlayerFishState(Enum): + FISHING = 0 + CAUGHT_FISH = 1 + CAUGHT_ENTITY = 2 + IN_GROUND = 3 + FAILED_ATTEMPT = 4 + REEL_IN = 5 + BITE = 6 + +@dataclass +class PlayerFishEventData: + player: player.Player + caught_uuid: Optional[str] + caught_type: str + hook_uuid: str + state: PlayerFishState + hand: common.Hand + exp_to_drop: int + cancelled: bool + +@dataclass +class PlayerEggThrowEventData: + player: player.Player + egg_uuid: str + hatching: bool + num_hatches: int + hatching_type: str + cancelled: bool + +class EntityInteractionAction(Enum): + INTERACT = 0 + ATTACK = 1 + INTERACT_AT = 2 + +class InteractAction(Enum): + LEFT_CLICK_BLOCK = 0 + LEFT_CLICK_AIR = 1 + RIGHT_CLICK_AIR = 2 + RIGHT_CLICK_BLOCK = 3 + +@dataclass +class PlayerInteractEventData: + player: player.Player + action: InteractAction + clicked_pos: Optional[Tuple[int, int, int]] + item: common.ItemStack + block: str + cancelled: bool + +@dataclass +class PlayerToggleSneakEventData: + player: player.Player + is_sneaking: bool + cancelled: bool + +@dataclass +class PlayerToggleSprintEventData: + player: player.Player + is_sprinting: bool + cancelled: bool + +@dataclass +class PlayerInteractUnknownEntityEventData: + player: player.Player + entity_id: int + action: EntityInteractionAction + cancelled: bool + +@dataclass +class BlockRedstoneEventData: + target_world: world.World + state_id: int + block_position: Tuple[int, int, int] + old_current: int + new_current: int + cancelled: bool + +@dataclass +class BlockBreakEventData: + player: Optional[player.Player] + block: str + block_position: Tuple[int, int, int] + exp: int + should_drop: bool + cancelled: bool + +@dataclass +class BlockBurnEventData: + igniting_block: str + block: str + cancelled: bool + +@dataclass +class BlockCanBuildEventData: + block_to_build: str + buildable: bool + player: player.Player + block: str + cancelled: bool + +@dataclass +class BlockGrowEventData: + target_world: world.World + old_block: str + old_state_id: int + new_block: str + new_state_id: int + block_position: Tuple[int, int, int] + cancelled: bool + +@dataclass +class BlockPlaceEventData: + player: player.Player + block_placed: str + block_placed_against: str + block_position: Tuple[int, int, int] + can_build: bool + cancelled: bool + +@dataclass +class ServerCommandEventData: + command: str + cancelled: bool + +@dataclass +class SpawnChangeEventData: + target_world: world.World + previous_position: Tuple[int, int, int] + previous_yaw: float + previous_pitch: float + new_position: Tuple[int, int, int] + new_yaw: float + new_pitch: float + +@dataclass +class ServerBroadcastEventData: + message: text.TextComponent + sender: text.TextComponent + cancelled: bool + +class EventType(Enum): + PLAYER_JOIN_EVENT = 0 + PLAYER_LEAVE_EVENT = 1 + PLAYER_LOGIN_EVENT = 2 + PLAYER_CHAT_EVENT = 3 + PLAYER_COMMAND_SEND_EVENT = 4 + PLAYER_PERMISSION_CHECK_EVENT = 5 + PLAYER_MOVE_EVENT = 6 + PLAYER_TELEPORT_EVENT = 7 + PLAYER_CHANGE_WORLD_EVENT = 8 + PLAYER_EXP_CHANGE_EVENT = 9 + PLAYER_ITEM_HELD_EVENT = 10 + PLAYER_CHANGED_MAIN_HAND_EVENT = 11 + PLAYER_GAMEMODE_CHANGE_EVENT = 12 + PLAYER_CUSTOM_PAYLOAD_EVENT = 13 + PLAYER_FISH_EVENT = 14 + PLAYER_EGG_THROW_EVENT = 15 + PLAYER_INTERACT_UNKNOWN_ENTITY_EVENT = 16 + PLAYER_INTERACT_EVENT = 17 + PLAYER_TOGGLE_SNEAK_EVENT = 18 + PLAYER_TOGGLE_SPRINT_EVENT = 19 + BLOCK_REDSTONE_EVENT = 20 + BLOCK_BREAK_EVENT = 21 + BLOCK_BURN_EVENT = 22 + BLOCK_CAN_BUILD_EVENT = 23 + BLOCK_GROW_EVENT = 24 + BLOCK_PLACE_EVENT = 25 + SERVER_COMMAND_EVENT = 26 + SPAWN_CHANGE_EVENT = 27 + SERVER_BROADCAST_EVENT = 28 + + +@dataclass +class Event_PlayerJoinEvent: + value: PlayerJoinEventData + + +@dataclass +class Event_PlayerLeaveEvent: + value: PlayerLeaveEventData + + +@dataclass +class Event_PlayerLoginEvent: + value: PlayerLoginEventData + + +@dataclass +class Event_PlayerChatEvent: + value: PlayerChatEventData + + +@dataclass +class Event_PlayerCommandSendEvent: + value: PlayerCommandSendEventData + + +@dataclass +class Event_PlayerPermissionCheckEvent: + value: PlayerPermissionCheckEventData + + +@dataclass +class Event_PlayerMoveEvent: + value: PlayerMoveEventData + + +@dataclass +class Event_PlayerTeleportEvent: + value: PlayerTeleportEventData + + +@dataclass +class Event_PlayerChangeWorldEvent: + value: PlayerChangeWorldEventData + + +@dataclass +class Event_PlayerExpChangeEvent: + value: PlayerExpChangeEventData + + +@dataclass +class Event_PlayerItemHeldEvent: + value: PlayerItemHeldEventData + + +@dataclass +class Event_PlayerChangedMainHandEvent: + value: PlayerChangedMainHandEventData + + +@dataclass +class Event_PlayerGamemodeChangeEvent: + value: PlayerGamemodeChangeEventData + + +@dataclass +class Event_PlayerCustomPayloadEvent: + value: PlayerCustomPayloadEventData + + +@dataclass +class Event_PlayerFishEvent: + value: PlayerFishEventData + + +@dataclass +class Event_PlayerEggThrowEvent: + value: PlayerEggThrowEventData + + +@dataclass +class Event_PlayerInteractUnknownEntityEvent: + value: PlayerInteractUnknownEntityEventData + + +@dataclass +class Event_PlayerInteractEvent: + value: PlayerInteractEventData + + +@dataclass +class Event_PlayerToggleSneakEvent: + value: PlayerToggleSneakEventData + + +@dataclass +class Event_PlayerToggleSprintEvent: + value: PlayerToggleSprintEventData + + +@dataclass +class Event_BlockRedstoneEvent: + value: BlockRedstoneEventData + + +@dataclass +class Event_BlockBreakEvent: + value: BlockBreakEventData + + +@dataclass +class Event_BlockBurnEvent: + value: BlockBurnEventData + + +@dataclass +class Event_BlockCanBuildEvent: + value: BlockCanBuildEventData + + +@dataclass +class Event_BlockGrowEvent: + value: BlockGrowEventData + + +@dataclass +class Event_BlockPlaceEvent: + value: BlockPlaceEventData + + +@dataclass +class Event_ServerCommandEvent: + value: ServerCommandEventData + + +@dataclass +class Event_SpawnChangeEvent: + value: SpawnChangeEventData + + +@dataclass +class Event_ServerBroadcastEvent: + value: ServerBroadcastEventData + + +Event = Union[Event_PlayerJoinEvent, Event_PlayerLeaveEvent, Event_PlayerLoginEvent, Event_PlayerChatEvent, Event_PlayerCommandSendEvent, Event_PlayerPermissionCheckEvent, Event_PlayerMoveEvent, Event_PlayerTeleportEvent, Event_PlayerChangeWorldEvent, Event_PlayerExpChangeEvent, Event_PlayerItemHeldEvent, Event_PlayerChangedMainHandEvent, Event_PlayerGamemodeChangeEvent, Event_PlayerCustomPayloadEvent, Event_PlayerFishEvent, Event_PlayerEggThrowEvent, Event_PlayerInteractUnknownEntityEvent, Event_PlayerInteractEvent, Event_PlayerToggleSneakEvent, Event_PlayerToggleSprintEvent, Event_BlockRedstoneEvent, Event_BlockBreakEvent, Event_BlockBurnEvent, Event_BlockCanBuildEvent, Event_BlockGrowEvent, Event_BlockPlaceEvent, Event_ServerCommandEvent, Event_SpawnChangeEvent, Event_ServerBroadcastEvent] + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/gui.py b/src/pumpkin_api/wit/wit_world/imports/gui.py new file mode 100644 index 0000000..f19d5e7 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/gui.py @@ -0,0 +1,61 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text + +class GuiType(Enum): + GENERIC_9X1 = 0 + GENERIC_9X2 = 1 + GENERIC_9X3 = 2 + GENERIC_9X4 = 3 + GENERIC_9X5 = 4 + GENERIC_9X6 = 5 + GENERIC_3X3 = 6 + CRAFTER_3X3 = 7 + ANVIL = 8 + BEACON = 9 + BLAST_FURNACE = 10 + BREWING_STAND = 11 + CRAFTING = 12 + ENCHANTMENT = 13 + FURNACE = 14 + GRINDSTONE = 15 + HOPPER = 16 + LECTERN = 17 + LOOM = 18 + MERCHANT = 19 + SHULKER_BOX = 20 + SMITHING = 21 + SMOKER = 22 + CARTOGRAPHY_TABLE = 23 + STONECUTTER = 24 + +class Gui: + + def __init__(self, type: GuiType, title: text.TextComponent) -> None: + raise NotImplementedError + + def set_item(self, slot: int, item: common.ItemStack) -> None: + raise NotImplementedError + def get_item(self, slot: int) -> Optional[common.ItemStack]: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/i18n.py b/src/pumpkin_api/wit/wit_world/imports/i18n.py new file mode 100644 index 0000000..4e50ace --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/i18n.py @@ -0,0 +1,35 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common + + +def translate(key: str, locale: common.Locale) -> str: + """ + Retrieves a translated string from the host. + + # Arguments + * `key`: The namespaced translation key (e.g. `pumpkin:my_key`). + * `locale`: The target locale. + + # Returns + The localized string, falling back to English or the key itself. + """ + raise NotImplementedError +def load_translations(namespace: str, json: str, locale: common.Locale) -> None: + """ + Loads custom translations from a JSON string. + + # Arguments + * `namespace`: The namespace to associate these keys with. + * `json`: A JSON string containing a flat map of translations. + * `locale`: The target locale for these translations. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit/wit_world/imports/logging.py b/src/pumpkin_api/wit/wit_world/imports/logging.py new file mode 100644 index 0000000..a2e59cf --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/logging.py @@ -0,0 +1,30 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class Level(Enum): + TRACE = 0 + DEBUG = 1 + INFO = 2 + WARN = 3 + ERROR = 4 + + +def log(level: Level, message: str) -> None: + """ + log any general purpose message + """ + raise NotImplementedError +def log_tracing(event: bytes) -> None: + """ + This function is meant to be used by the tracing crate. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit/wit_world/imports/permission.py b/src/pumpkin_api/wit/wit_world/imports/permission.py new file mode 100644 index 0000000..4718bfe --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/permission.py @@ -0,0 +1,71 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class PermissionLevel(Enum): + """ + Represents the player's permission level + + Permission levels determine the player's access to commands and server operations. + Each numeric level corresponds to a specific role: + - `Zero`: `normal`: Player can use basic commands. + - `One`: `moderator`: Player can bypass spawn protection. + - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks. + - `Three`: `admin`: Player or executor can use commands related to multiplayer management. + - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. + """ + ZERO = 0 + ONE = 1 + TWO = 2 + THREE = 3 + FOUR = 4 + + +@dataclass +class PermissionDefault_Deny: + pass + + +@dataclass +class PermissionDefault_Allow: + pass + + +@dataclass +class PermissionDefault_Op: + value: PermissionLevel + + +PermissionDefault = Union[PermissionDefault_Deny, PermissionDefault_Allow, PermissionDefault_Op] +""" +Describes the default behavior for permissions +""" + + +@dataclass +class PermissionChild: + """ + A child permission entry + """ + node: str + value: bool + +@dataclass +class Permission: + """ + Defines a permission node in the system + """ + node: str + description: str + default: PermissionDefault + children: List[PermissionChild] + + diff --git a/src/pumpkin_api/wit/wit_world/imports/player.py b/src/pumpkin_api/wit/wit_world/imports/player.py new file mode 100644 index 0000000..b8c2bd9 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/player.py @@ -0,0 +1,350 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Represents a player connected to the Minecraft server. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import permission +from ..imports import text +from ..imports import gui +from ..imports import world + +@dataclass +class PlayerAbilities: + """ + Various abilities a player can have. + """ + invulnerable: bool + flying: bool + allow_flying: bool + creative: bool + allow_modify_world: bool + fly_speed: float + walk_speed: float + +class Player: + """ + A handle to a player on the server. + """ + + def get_id(self) -> str: + """ + Returns the player's unique UUID as a string. + """ + raise NotImplementedError + def get_name(self) -> str: + """ + Returns the player's username. + """ + raise NotImplementedError + def get_position(self) -> Tuple[float, float, float]: + """ + Basic state + Returns the player's current position in the world. + """ + raise NotImplementedError + def get_yaw(self) -> float: + """ + Returns the player's current yaw (horizontal rotation). + """ + raise NotImplementedError + def get_pitch(self) -> float: + """ + Returns the player's current pitch (vertical rotation). + """ + raise NotImplementedError + def get_world(self) -> world.World: + """ + Returns the world the player is currently in. + """ + raise NotImplementedError + def get_gamemode(self) -> common.GameMode: + """ + Returns the player's current game mode. + """ + raise NotImplementedError + def set_gamemode(self, mode: common.GameMode) -> bool: + """ + Changes the player's game mode. Returns true if successful. + """ + raise NotImplementedError + def get_locale(self) -> str: + """ + Returns the player's locale (e.g., "en_us"). + """ + raise NotImplementedError + def get_ping(self) -> int: + """ + Returns the player's current ping in milliseconds. + """ + raise NotImplementedError + def get_permission_level(self) -> permission.PermissionLevel: + """ + Permissions & metadata + Returns the player's permission level (0-4). + """ + raise NotImplementedError + def set_permission_level(self, level: permission.PermissionLevel) -> None: + """ + Sets the player's permission level. + """ + raise NotImplementedError + def has_permission(self, node: str) -> bool: + """ + Checks if the player has a specific permission node. + """ + raise NotImplementedError + def get_display_name(self) -> text.TextComponent: + """ + Returns the player's display name, which may include formatting. + """ + raise NotImplementedError + def send_system_message(self, text: text.TextComponent, overlay: bool) -> None: + """ + Messaging & titles + Sends a system message to the player's chat. + """ + raise NotImplementedError + def show_title(self, text: text.TextComponent) -> None: + """ + Shows a large title on the player's screen. + """ + raise NotImplementedError + def show_subtitle(self, text: text.TextComponent) -> None: + """ + Shows a smaller subtitle on the player's screen. + """ + raise NotImplementedError + def show_actionbar(self, text: text.TextComponent) -> None: + """ + Shows a message in the action bar area. + """ + raise NotImplementedError + def send_title_animation(self, fade_in: int, stay: int, fade_out: int) -> None: + """ + Sets the fade-in, stay, and fade-out times for titles. + """ + raise NotImplementedError + def teleport(self, position: Tuple[float, float, float], yaw: Optional[float], pitch: Optional[float], world: world.World) -> None: + """ + Teleport & kick + Teleports the player to a position in a world. + """ + raise NotImplementedError + def teleport_world(self, world_ref: world.World, position: Tuple[float, float, float], yaw: Optional[float], pitch: Optional[float]) -> None: + """ + Teleports the player to another world at a specific position. + """ + raise NotImplementedError + def kick(self, message: text.TextComponent) -> None: + """ + Kicks the player from the server with the specified reason. + """ + raise NotImplementedError + def respawn(self) -> None: + """ + Forces the player to respawn. + """ + raise NotImplementedError + def open_gui(self, gui_ref: gui.Gui) -> None: + """ + Opens a GUI for the player. + """ + raise NotImplementedError + def ban(self, reason: Optional[text.TextComponent]) -> None: + """ + Bans the player with an optional reason. + """ + raise NotImplementedError + def ban_ip(self, reason: Optional[text.TextComponent]) -> None: + """ + Bans the player's IP address with an optional reason. + """ + raise NotImplementedError + def get_selected_slot(self) -> int: + """ + Inventory + Returns the index of the currently selected hotbar slot (0-8). + """ + raise NotImplementedError + def get_item_in_hand(self, hand: common.Hand) -> Optional[common.ItemStack]: + """ + Returns the item currently in the specified hand. + """ + raise NotImplementedError + def get_inventory_item(self, slot: int) -> Optional[common.ItemStack]: + """ + Returns the item in the specified inventory slot. + """ + raise NotImplementedError + def get_health(self) -> float: + """ + Status + Returns the player's current health. + """ + raise NotImplementedError + def set_health(self, health: float) -> None: + """ + Sets the player's current health. + """ + raise NotImplementedError + def get_max_health(self) -> float: + """ + Returns the player's maximum health. + """ + raise NotImplementedError + def set_max_health(self, max_health: float) -> None: + """ + Sets the player's maximum health. + """ + raise NotImplementedError + def get_food_level(self) -> int: + """ + Returns the player's food level (0-20). + """ + raise NotImplementedError + def set_food_level(self, food_level: int) -> None: + """ + Sets the player's food level. + """ + raise NotImplementedError + def get_saturation(self) -> float: + """ + Returns the player's food saturation level. + """ + raise NotImplementedError + def set_saturation(self, saturation: float) -> None: + """ + Sets the player's food saturation level. + """ + raise NotImplementedError + def get_exhaustion(self) -> float: + """ + Returns the player's food exhaustion level. + """ + raise NotImplementedError + def set_exhaustion(self, exhaustion: float) -> None: + """ + Sets the player's food exhaustion level. + """ + raise NotImplementedError + def get_absorption(self) -> float: + """ + Returns the player's current absorption amount. + """ + raise NotImplementedError + def set_absorption(self, absorption: float) -> None: + """ + Sets the player's current absorption amount. + """ + raise NotImplementedError + def get_experience_level(self) -> int: + """ + Returns the player's total experience level. + """ + raise NotImplementedError + def get_experience_progress(self) -> float: + """ + Returns the player's progress toward the next level (0.0 to 1.0). + """ + raise NotImplementedError + def get_experience_points(self) -> int: + """ + Returns the total experience points the player has. + """ + raise NotImplementedError + def set_experience_level(self, level: int) -> None: + """ + Sets the player's total experience level. + """ + raise NotImplementedError + def set_experience_progress(self, progress: float) -> None: + """ + Sets the player's progress toward the next level. + """ + raise NotImplementedError + def set_experience_points(self, points: int) -> None: + """ + Sets the player's total experience points. + """ + raise NotImplementedError + def add_experience_levels(self, levels: int) -> None: + """ + Adds experience levels to the player. + """ + raise NotImplementedError + def add_experience_points(self, points: int) -> None: + """ + Adds experience points to the player. + """ + raise NotImplementedError + def is_sneaking(self) -> bool: + """ + Returns whether the player is currently sneaking. + """ + raise NotImplementedError + def set_sneaking(self, sneaking: bool) -> None: + """ + Sets whether the player is currently sneaking. + """ + raise NotImplementedError + def is_sprinting(self) -> bool: + """ + Returns whether the player is currently sprinting. + """ + raise NotImplementedError + def set_sprinting(self, sprinting: bool) -> None: + """ + Sets whether the player is currently sprinting. + """ + raise NotImplementedError + def is_on_ground(self) -> bool: + """ + Returns whether the player is currently on the ground. + """ + raise NotImplementedError + def is_flying(self) -> bool: + """ + Returns whether the player is currently flying. + """ + raise NotImplementedError + def set_flying(self, flying: bool) -> None: + """ + Sets whether the player should be flying. + """ + raise NotImplementedError + def get_abilities(self) -> PlayerAbilities: + """ + Returns the player's current abilities. + """ + raise NotImplementedError + def set_abilities(self, abilities: PlayerAbilities) -> None: + """ + Updates the player's abilities. + """ + raise NotImplementedError + def get_ip(self) -> str: + """ + Returns the player's IP address. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/scheduler.py b/src/pumpkin_api/wit/wit_world/imports/scheduler.py new file mode 100644 index 0000000..8b8a5b1 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/scheduler.py @@ -0,0 +1,44 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Provides functionality to schedule tasks to be executed later on the server. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + + +def schedule_delayed_task(handler_id: int, delay_ticks: int) -> int: + """ + Schedules a task to be executed once after the specified number of ticks. + + * `handler-id`: The unique ID of the handler to call when the task executes. + * `delay-ticks`: The number of game ticks to wait before execution (20 ticks = 1 second). + + Returns a unique task ID that can be used to cancel the task. + """ + raise NotImplementedError +def schedule_repeating_task(handler_id: int, delay_ticks: int, period_ticks: int) -> int: + """ + Schedules a task to be executed repeatedly. + + * `handler-id`: The unique ID of the handler to call when the task executes. + * `delay-ticks`: The number of game ticks to wait before the first execution. + * `period-ticks`: The number of game ticks between subsequent executions. + + Returns a unique task ID that can be used to cancel the task. + """ + raise NotImplementedError +def cancel_task(task_id: int) -> None: + """ + Cancels a scheduled task if it hasn't finished yet. + + * `task-id`: The ID of the task to cancel. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit/wit_world/imports/scoreboard.py b/src/pumpkin_api/wit/wit_world/imports/scoreboard.py new file mode 100644 index 0000000..c75b352 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/scoreboard.py @@ -0,0 +1,95 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text + +class RenderType(Enum): + INTEGER = 0 + HEARTS = 1 + +class DisplaySlot(Enum): + PLAYER_LIST = 0 + SIDEBAR = 1 + BELOW_NAME = 2 + SIDEBAR_TEAM_BLACK = 3 + SIDEBAR_TEAM_DARK_BLUE = 4 + SIDEBAR_TEAM_DARK_GREEN = 5 + SIDEBAR_TEAM_DARK_AQUA = 6 + SIDEBAR_TEAM_DARK_RED = 7 + SIDEBAR_TEAM_DARK_PURPLE = 8 + SIDEBAR_TEAM_GOLD = 9 + SIDEBAR_TEAM_GRAY = 10 + SIDEBAR_TEAM_DARK_GRAY = 11 + SIDEBAR_TEAM_BLUE = 12 + SIDEBAR_TEAM_GREEN = 13 + SIDEBAR_TEAM_AQUA = 14 + SIDEBAR_TEAM_RED = 15 + SIDEBAR_TEAM_LIGHT_PURPLE = 16 + SIDEBAR_TEAM_YELLOW = 17 + SIDEBAR_TEAM_WHITE = 18 + +class NametagVisibility(Enum): + ALWAYS = 0 + NEVER = 1 + HIDE_FOR_OTHER_TEAMS = 2 + HIDE_FOR_OWN_TEAM = 3 + +class CollisionRule(Enum): + ALWAYS = 0 + NEVER = 1 + PUSH_OTHER_TEAMS = 2 + PUSH_OWN_TEAM = 3 + +@dataclass +class TeamSettings: + display_name: text.TextComponent + friendly_fire: bool + see_friendly_invisibles: bool + nametag_visibility: NametagVisibility + collision_rule: CollisionRule + color: common.NamedColor + prefix: text.TextComponent + suffix: text.TextComponent + +class Scoreboard: + + def add_objective(self, name: str, display_name: text.TextComponent, render_type: RenderType) -> None: + raise NotImplementedError + def remove_objective(self, name: str) -> None: + raise NotImplementedError + def set_display_slot(self, slot: DisplaySlot, objective_name: str) -> None: + raise NotImplementedError + def update_score(self, entity_name: str, objective_name: str, value: int) -> None: + raise NotImplementedError + def remove_score(self, entity_name: str, objective_name: str) -> None: + raise NotImplementedError + def create_team(self, name: str, settings: TeamSettings) -> None: + raise NotImplementedError + def remove_team(self, name: str) -> None: + raise NotImplementedError + def update_team(self, name: str, settings: TeamSettings) -> None: + raise NotImplementedError + def add_player_to_team(self, team_name: str, player_name: str) -> None: + raise NotImplementedError + def remove_player_from_team(self, team_name: str, player_name: str) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/server.py b/src/pumpkin_api/wit/wit_world/imports/server.py new file mode 100644 index 0000000..fcde6e2 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/server.py @@ -0,0 +1,75 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import player + +class Difficulty(Enum): + """ + Represents the difficulty setting of a Minecraft server. + """ + PEACEFUL = 0 + EASY = 1 + NORMAL = 2 + HARD = 3 + +class Server: + """ + Represents the global server instance. + """ + + def get_difficulty(self) -> Difficulty: + """ + Returns the current difficulty level of the server. + """ + raise NotImplementedError + def get_player_count(self) -> int: + """ + Returns the total number of players currently connected to the server. + """ + raise NotImplementedError + def get_mspt(self) -> float: + """ + Returns the rolling average Milliseconds Per Tick (MSPT). + A value below 50.0 is required for a stable 20 TPS. + """ + raise NotImplementedError + def get_tps(self) -> float: + """ + Returns the effective Ticks Per Second (TPS). + Ideal value is 20.0. + """ + raise NotImplementedError + def get_all_players(self) -> List[player.Player]: + """ + Returns a list of all online players across all worlds. + """ + raise NotImplementedError + def get_player_by_name(self, name: str) -> Optional[player.Player]: + """ + Searches for a player by their exact name. + """ + raise NotImplementedError + def get_player_by_uuid(self, id: str) -> Optional[player.Player]: + """ + Searches for a player by their UUID string. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/text.py b/src/pumpkin_api/wit/wit_world/imports/text.py new file mode 100644 index 0000000..6520805 --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/text.py @@ -0,0 +1,89 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common + +class TextComponent: + + @classmethod + def text(cls, plain: str) -> Self: + raise NotImplementedError + @classmethod + def translate(cls, key: str, with_: List[Self]) -> Self: + raise NotImplementedError + def add_child(self, child: Self) -> None: + raise NotImplementedError + def add_text(self, text: str) -> None: + raise NotImplementedError + def get_text(self) -> str: + raise NotImplementedError + def encode(self) -> bytes: + raise NotImplementedError + def color_named(self, color: common.NamedColor) -> None: + """ + Style + """ + raise NotImplementedError + def color_rgb(self, color: common.RgbColor) -> None: + raise NotImplementedError + def bold(self, value: bool) -> None: + raise NotImplementedError + def italic(self, value: bool) -> None: + raise NotImplementedError + def underlined(self, value: bool) -> None: + raise NotImplementedError + def strikethrough(self, value: bool) -> None: + raise NotImplementedError + def obfuscated(self, value: bool) -> None: + raise NotImplementedError + def insertion(self, text: str) -> None: + """ + Text inserted into chat when shift-clicked + """ + raise NotImplementedError + def font(self, font: str) -> None: + raise NotImplementedError + def shadow_color(self, color: common.ArgbColor) -> None: + raise NotImplementedError + def click_open_url(self, url: str) -> None: + """ + Click events + """ + raise NotImplementedError + def click_run_command(self, command: str) -> None: + raise NotImplementedError + def click_suggest_command(self, command: str) -> None: + raise NotImplementedError + def click_copy_to_clipboard(self, text: str) -> None: + raise NotImplementedError + def hover_show_text(self, text: Self) -> None: + """ + Hover events + """ + raise NotImplementedError + def hover_show_item(self, item: str) -> None: + """ + Item data as SNBT string + """ + raise NotImplementedError + def hover_show_entity(self, entity_type: str, id: str, name: Optional[Self]) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit/wit_world/imports/world.py b/src/pumpkin_api/wit/wit_world/imports/world.py new file mode 100644 index 0000000..fa5a13a --- /dev/null +++ b/src/pumpkin_api/wit/wit_world/imports/world.py @@ -0,0 +1,215 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import text +from ..imports import scoreboard + +class PistonBehavior(Enum): + """ + Defines how a block reacts when pushed by a piston. + """ + NORMAL = 0 + DESTROY = 1 + BLOCK = 2 + IGNORE = 3 + PUSH_ONLY = 4 + +class SoundCategory(Enum): + """ + Categories for sounds to control their volume independently. + """ + MASTER = 0 + MUSIC = 1 + RECORDS = 2 + WEATHER = 3 + BLOCKS = 4 + HOSTILE = 5 + NEUTRAL = 6 + PLAYERS = 7 + AMBIENT = 8 + VOICE = 9 + +class ExplosionInteraction(Enum): + """ + Defines how an explosion interacts with blocks in the world. + """ + NONE = 0 + BLOCK = 1 + MOB = 2 + TNT = 3 + TRIGGER = 4 + +class BlockFlags(Flag): + """ + Flags used to control block updates and notifications. + """ + NOTIFY_NEIGHBORS = auto() + NOTIFY_LISTENERS = auto() + FORCE_STATE = auto() + SKIP_DROPS = auto() + MOVED = auto() + SKIP_REDSTONE_WIRE_STATE_REPLACEMENT = auto() + SKIP_BLOCK_ENTITY_REPLACED_CALLBACK = auto() + SKIP_BLOCK_ADDED_CALLBACK = auto() + +@dataclass +class BlockState: + """ + Detailed information about a block's state. + """ + id: int + luminance: int + opacity: int + hardness: float + is_air: bool + is_liquid: bool + is_solid: bool + is_full_cube: bool + piston_behavior: PistonBehavior + has_random_ticks: bool + +@dataclass +class BlockStateId: + """ + A simple wrapper for a block state ID. + """ + id: int + +@dataclass +class BlockPos: + """ + A position in the world grid. + """ + x: int + y: int + z: int + +class World: + """ + Represents a Minecraft world (dimension). + """ + + def get_id(self) -> str: + """ + Returns the unique identifier of this world. + """ + raise NotImplementedError + def get_block_state_id(self, pos: BlockPos) -> int: + """ + Gets the block state ID at the specified position. + """ + raise NotImplementedError + def get_block_state(self, pos: BlockPos) -> BlockState: + """ + Gets detailed block state information at the specified position. + """ + raise NotImplementedError + def set_block_state(self, pos: BlockPos, state: int, update_flags: BlockFlags) -> None: + """ + Sets the block state at the specified position. + """ + raise NotImplementedError + def get_time_of_day(self) -> int: + """ + Returns the current time of day in ticks. + """ + raise NotImplementedError + def set_time_of_day(self, time: int) -> None: + """ + Sets the current time of day in ticks. + """ + raise NotImplementedError + def get_world_age(self) -> int: + """ + Returns the total age of the world in ticks. + """ + raise NotImplementedError + def get_dimension(self) -> str: + """ + Returns the dimension type (e.g., "minecraft:overworld"). + """ + raise NotImplementedError + def get_top_block_y(self, x: int, z: int) -> int: + """ + Gets the highest non-air block Y coordinate at the specified X and Z. + """ + raise NotImplementedError + def get_motion_blocking_height(self, x: int, z: int) -> int: + """ + Gets the highest motion-blocking block Y coordinate. + """ + raise NotImplementedError + def is_raining(self) -> bool: + """ + Returns whether it is currently raining or snowing. + """ + raise NotImplementedError + def set_raining(self, raining: bool) -> None: + """ + Sets whether it should be raining or snowing. + """ + raise NotImplementedError + def is_thundering(self) -> bool: + """ + Returns whether there is currently a thunderstorm. + """ + raise NotImplementedError + def set_thundering(self, thundering: bool) -> None: + """ + Sets whether there should be a thunderstorm. + """ + raise NotImplementedError + def broadcast_system_message(self, message: text.TextComponent, overlay: bool) -> None: + """ + Broadcasts a system message to all players in this world. + """ + raise NotImplementedError + def get_scoreboard(self) -> scoreboard.Scoreboard: + """ + Returns the scoreboard associated with this world. + """ + raise NotImplementedError + def play_sound(self, sound: str, category: SoundCategory, pos: Tuple[float, float, float], volume: float, pitch: float) -> None: + """ + Plays a sound at the specified position for all players in the world. + """ + raise NotImplementedError + def spawn_particle(self, particle: str, pos: Tuple[float, float, float], offset: Tuple[float, float, float], max_speed: float, count: int) -> None: + """ + Spawns particles at the specified position. + """ + raise NotImplementedError + def create_explosion(self, pos: Tuple[float, float, float], power: float, create_fire: bool, interaction: ExplosionInteraction) -> None: + """ + Creates an explosion at the specified position. + """ + raise NotImplementedError + def get_sea_level(self) -> int: + """ + Returns the sea level of this world. + """ + raise NotImplementedError + def get_min_y(self) -> int: + """ + Returns the minimum Y coordinate (bottom of the world). + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/command.wit b/src/pumpkin_api/wit_files/command.wit new file mode 100644 index 0000000..e1265fd --- /dev/null +++ b/src/pumpkin_api/wit_files/command.wit @@ -0,0 +1,189 @@ +interface command { + use player.{player}; + use %world.{%world}; + use entity.{command-block-entity, entity}; + use common.{position, block-position, locale}; + use server.{server, difficulty}; + use text.{text-component}; + + enum gamemode { + survival, + creative, + adventure, + spectator, + } + + enum bossbar-color { + pink, + blue, + red, + green, + yellow, + purple, + white, + } + + enum bossbar-style { + no-division, + notches6, + notches10, + notches12, + notches20, + } + + enum sound-category { + master, + music, + records, + weather, + blocks, + hostile, + neutral, + players, + ambient, + voice, + ui, + } + + enum entity-anchor { + feet, + eyes, + } + + variant number { + float64(f64), + float32(f32), + int32(s32), + int64(s64), + } + + variant not-in-bounds { + lower-bound(tuple), + upper-bound(tuple), + } + + variant arg { + entities(list), + entity(entity), + players(list), + block-pos(block-position), + pos3d(position), + pos2d(tuple), + rotation(tuple), + game-mode(gamemode), + difficulty(difficulty), + item(string), + item-predicate(string), + resource-location(string), + block(string), + block-predicate(string), + bossbar-color(bossbar-color), + bossbar-style(bossbar-style), + particle(string), + msg(string), + text-component(text-component), + time(s32), + num(result), + %bool(bool), + simple(string), + sound-category(sound-category), + damage-type(string), + effect(string), + enchantment(string), + entity-anchor(entity-anchor), + } + + resource consumed-args { + get-value: func(key: string) -> arg; + } + + variant command-sender-type { + rcon, + console, + player(player), + command-block(tuple), + dummy, + } + + enum permission-level { + zero, + one, + two, + three, + four, + } + + resource command-sender { + get-command-sender-type: func() -> command-sender-type; + send-message: func(text: text-component); + set-success-count: func(count: s32); + is-player: func() -> bool; + is-console: func() -> bool; + as-player: func() -> option; + permission-level: func() -> permission-level; + has-permission-level: func(level: permission-level) -> bool; + has-permission: func(server: borrow, node: string) -> bool; + position: func() -> option; + %world: func() -> option<%world>; + get-locale: func() -> locale; + should-receive-feedback: func() -> bool; + should-broadcast-console-to-ops: func() -> bool; + should-track-output: func() -> bool; + } + + enum string-type { + single-word, + quotable, + greedy, + } + + variant argument-type { + %bool, + float(tuple, option>), + double(tuple, option>), + integer(tuple, option>), + long(tuple, option>), + %string(string-type), + entities, + entity, + %players, + game-profile, + block-pos, + position3d, + position2d, + block-state, + block-predicate, + item, + item-predicate, + component, + rotation, + %resource-location, + entity-anchor, + gamemode, + difficulty, + time(s32), + %resource(string), + } + + variant command-error { + invalid-consumption(option), + invalid-requirement, + permission-denied, + command-failed(text-component), + } + + resource command-node { + literal: static func(name: string) -> command-node; + argument: static func(name: string, arg-type: argument-type) -> command-node; + then: func(node: command-node); + execute-with-handler-id: func(handler-id: u32); + require-with-handler-id: func(handler-id: u32); + } + + resource command { + /// First name is primary, rest are aliases + constructor(names: list, description: string); + then: func(node: command-node); + execute-with-handler-id: func(handler-id: u32); + } +} diff --git a/src/pumpkin_api/wit_files/common.wit b/src/pumpkin_api/wit_files/common.wit new file mode 100644 index 0000000..6eab865 --- /dev/null +++ b/src/pumpkin_api/wit_files/common.wit @@ -0,0 +1,201 @@ +/// Provides common types and utilities used across the entire plugin API. +interface common { + /// Serialized text component as a postcard byte array. + /// @deprecated Use the text-component resource from the text interface instead. + type raw-text-component = list; + + /// A position in the world grid (integers). + type block-position = tuple; + + /// A precise coordinate in the world (floats). + type position = tuple; + + /// Represents the hands of a player. + enum hand { + left, + right, + } + + /// Represents the game modes available in Minecraft. + enum game-mode { + survival, + creative, + adventure, + spectator, + } + + /// The 16 base Minecraft colors. + enum named-color { + black, + dark-blue, + dark-green, + dark-aqua, + dark-red, + dark-purple, + gold, + gray, + dark-gray, + blue, + green, + aqua, + red, + light-purple, + yellow, + white, + } + + /// An RGB color represented by its red, green, and blue components. + record rgb-color { + r: u8, + g: u8, + b: u8, + } + + /// An ARGB color with alpha (transparency) channel. + record argb-color { + a: u8, + r: u8, + g: u8, + b: u8, + } + + /// Represents a stack of items with its type and count. + record item-stack { + /// The unique registry key (e.g., "minecraft:diamond"). + registry-key: string, + /// The number of items in the stack. + count: u8, + } + + /// All Minecraft-supported locales. + enum locale { + af-za, + ar-sa, + ast-es, + az-az, + ba-ru, + bar, + be-by, + bg-bg, + br-fr, + brb, + bs-ba, + ca-es, + cs-cz, + cy-gb, + da-dk, + de-at, + de-ch, + de-de, + el-gr, + en-au, + en-ca, + en-gb, + en-nz, + en-pt, + en-ud, + en-us, + enp, + enws, + eo-uy, + es-ar, + es-cl, + es-ec, + es-es, + es-mx, + es-uy, + es-ve, + esan, + et-ee, + eu-es, + fa-ir, + fi-fi, + fil-ph, + fo-fo, + fr-ca, + fr-fr, + fra-de, + fur-it, + fy-nl, + ga-ie, + gd-gb, + gl-es, + haw-us, + he-il, + hi-in, + hr-hr, + hu-hu, + hy-am, + id-id, + ig-ng, + io-en, + is-is, + isv, + it-it, + ja-jp, + jbo-en, + ka-ge, + kk-kz, + kn-in, + ko-kr, + ksh, + kw-gb, + la-la, + lb-lu, + li-li, + lmo, + lo-la, + lol-us, + lt-lt, + lv-lv, + lzh, + mk-mk, + mn-mn, + ms-my, + mt-mt, + nah, + nds-de, + nl-be, + nl-nl, + nn-no, + no-no, + oc-fr, + ovd, + pl-pl, + pt-br, + pt-pt, + qya-aa, + ro-ro, + rpr, + ru-ru, + ry-ua, + sah-sah, + se-no, + sk-sk, + sl-si, + so-so, + sq-al, + sr-cs, + sr-sp, + sv-se, + sxu, + szl, + ta-in, + th-th, + tl-ph, + tlh-aa, + tok, + tr-tr, + tt-ru, + uk-ua, + val-es, + vec-it, + vi-vn, + yi-de, + yo-ng, + zh-cn, + zh-hk, + zh-tw, + zlm-arab, + } +} diff --git a/src/pumpkin_api/wit_files/componentize_py_async_support/__init__.py b/src/pumpkin_api/wit_files/componentize_py_async_support/__init__.py new file mode 100644 index 0000000..a7bd016 --- /dev/null +++ b/src/pumpkin_api/wit_files/componentize_py_async_support/__init__.py @@ -0,0 +1,529 @@ +import asyncio +import componentize_py_runtime +import subprocess + +from os import PathLike +from socket import AddressFamily, AddressInfo, SocketKind, socket +from concurrent.futures import Executor +from componentize_py_types import Result, Ok, Err +from dataclasses import dataclass +from typing import Any, Optional, TypeVar, TypeVarTuple, Callable, IO, Literal, cast +from contextvars import ContextVar, Context +from collections.abc import Coroutine, Awaitable, Sequence +from asyncio.protocols import BaseProtocol +from asyncio.transports import Transport, WriteTransport, DatagramTransport, SubprocessTransport, ReadTransport +from asyncio.base_events import Server + +try: + from ssl import SSLContext +except: + pass + +@dataclass +class _FutureState: + waitable_set: int | None + futures: dict[int, Any] + handles: list[asyncio.Handle] + pending_count: int + +class _ReturnCode: + COMPLETED = 0 + DROPPED = 1 + CANCELLED = 2 + +class _CallbackCode: + EXIT = 0 + YIELD = 1 + WAIT = 2 + POLL = 3 + +class _Event: + NONE = 0 + SUBTASK = 1 + STREAM_READ = 2 + STREAM_WRITE = 3 + FUTURE_READ = 4 + FUTURE_WRITE = 5 + CANCELLED = 6 + +class _Status: + STARTING = 0 + STARTED = 1 + RETURNED = 2 + START_CANCELLED = 3 + RETURN_CANCELLED = 4 + +_T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") +_ProtocolT = TypeVar("_ProtocolT", bound=BaseProtocol) + +async def _noop() -> None: + pass + +class _Loop(asyncio.AbstractEventLoop): + def __init__(self) -> None: + self.running: bool = False + self.exception: Optional[Any] = None + + def poll(self, future_state: _FutureState) -> None: + while True: + handles = future_state.handles + future_state.handles = [] + for handle in handles: + if not handle._cancelled: + handle._run() + + if self.exception is not None: + raise self.exception + + if len(handles) == 0 and len(future_state.handles) == 0: + return + + def get_debug(self) -> bool: + return False + + def is_running(self) -> bool: + return self.running + + def is_closed(self) -> bool: + return not self.running + + def stop(self) -> None: + self.running = False + + def close(self) -> None: + self.running = False + + def shutdown_asyncgens(self) -> Coroutine[Any, Any, None]: + return _noop() + + def call_exception_handler(self, context: dict[str, Any]) -> None: + self.exception = context.get('exception', None) + + def call_soon(self, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.Handle: + global _future_state + + if context is not None: + future_state = context[_future_state] + handle = asyncio.Handle(callback, args, self, context) + future_state.handles.append(handle) + return handle + else: + raise AssertionError + + def create_task(self, coroutine: Coroutine[Any, Any, _T], name: str | None = None, context: Context | None = None) -> asyncio.Task[_T]: + return asyncio.Task(coroutine, loop=self, context=context) + + def create_future(self) -> asyncio.Future[Any]: + return asyncio.Future(loop=self) + + # The remaining methods should be irrelevant for our purposes and thus unimplemented + + def run_until_complete(self, future: Awaitable[_T]) -> _T: + raise NotImplementedError + + def run_forever(self) -> None: + raise NotImplementedError + + async def shutdown_default_executor(self) -> None: + raise NotImplementedError + + def call_later(self, + delay: float, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.TimerHandle: + raise NotImplementedError + + def call_at(self, + when: float, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.TimerHandle: + raise NotImplementedError + + def time(self) -> float: + raise NotImplementedError + + def call_soon_threadsafe(self, + callback: Callable[[*_Ts], object], + *args: *_Ts, + context: Context | None = None) -> asyncio.Handle: + raise NotImplementedError + + def run_in_executor(self, + executor: Executor | None, + func: Callable[[*_Ts], _T], + *args: *_Ts) -> asyncio.Future[_T]: + raise NotImplementedError + + def set_default_executor(self, executor: Executor) -> None: + raise NotImplementedError + + async def getaddrinfo( + self, + host: bytes | str | None, + port: bytes | str | int | None, + *, + family: int = 0, + type: int = 0, + proto: int = 0, + flags: int = 0, + ) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int]]]: + raise NotImplementedError + + async def getnameinfo(self, + sockaddr: tuple[str, int] | tuple[str, int, int, int], + flags: int = 0) -> tuple[str, str]: + raise NotImplementedError + + async def create_connection( + self, + protocol_factory: Callable[[], _ProtocolT], + host: str | None = ..., + port: int | None = ..., + *, + ssl: bool | SSLContext | None = None, + family: int = 0, + proto: int = 0, + flags: int = 0, + sock: socket | None = None, + local_addr: tuple[str, int] | None = None, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + happy_eyeballs_delay: float | None = None, + interleave: int | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_server( + self, + protocol_factory: Callable[[], BaseProtocol], + host: str | Sequence[str] | None = None, + port: int | None = None, + *, + family: int = AddressFamily.AF_UNSPEC, + flags: int = AddressInfo.AI_PASSIVE, + sock: socket | None = ..., + backlog: int = 100, + ssl: bool | SSLContext | None = None, + reuse_address: bool | None = None, + reuse_port: bool | None = None, + keep_alive: bool | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + start_serving: bool = True, + ) -> Server: + raise NotImplementedError + + async def sendfile(self, + transport: WriteTransport, + file: IO[bytes], + offset: int = 0, + count: int | None = None, + *, + fallback: bool = True + ) -> int: + raise NotImplementedError + + async def start_tls( + self, + transport: WriteTransport, + protocol: BaseProtocol, + sslcontext: SSLContext, + *, + server_side: bool = False, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> Transport | None: + raise NotImplementedError + + async def create_unix_connection( + self, + protocol_factory: Callable[[], _ProtocolT], + path: str | None = None, + *, + ssl: bool | SSLContext | None = None, + sock: socket | None = None, + server_hostname: str | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_unix_server( + self, + protocol_factory: Callable[[], BaseProtocol], + path: str | PathLike[str] | None = None, + *, + sock: socket | None = None, + backlog: int = 100, + ssl: bool | SSLContext | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + start_serving: bool = True, + ) -> Server: + raise NotImplementedError + + async def connect_accepted_socket( + self, + protocol_factory: Callable[[], _ProtocolT], + sock: socket, + *, + ssl: bool | SSLContext | None = None, + ssl_handshake_timeout: float | None = None, + ssl_shutdown_timeout: float | None = None, + ) -> tuple[Transport, _ProtocolT]: + raise NotImplementedError + + async def create_datagram_endpoint( + self, + protocol_factory: Callable[[], _ProtocolT], + local_addr: tuple[str, int] | str | None = None, + remote_addr: tuple[str, int] | str | None = None, + *, + family: int = 0, + proto: int = 0, + flags: int = 0, + reuse_address: bool | None = None, + reuse_port: bool | None = None, + allow_broadcast: bool | None = None, + sock: socket | None = None, + ) -> tuple[DatagramTransport, _ProtocolT]: + raise NotImplementedError + + async def connect_read_pipe(self, + protocol_factory: Callable[[], _ProtocolT], + pipe: Any) -> tuple[ReadTransport, _ProtocolT]: + raise NotImplementedError + + async def connect_write_pipe(self, + protocol_factory: Callable[[], _ProtocolT], + pipe: Any) -> tuple[WriteTransport, _ProtocolT]: + raise NotImplementedError + + async def subprocess_shell( + self, + protocol_factory: Callable[[], _ProtocolT], + cmd: bytes | str, + *, + stdin: int | IO[Any] | None = -1, + stdout: int | IO[Any] | None = -1, + stderr: int | IO[Any] | None = -1, + universal_newlines: Literal[False] = False, + shell: Literal[True] = True, + bufsize: Literal[0] = 0, + encoding: None = None, + errors: None = None, + text: Literal[False] | None = None, + **kwargs: Any, + ) -> tuple[SubprocessTransport, _ProtocolT]: + raise NotImplementedError + + async def subprocess_exec( + self, + protocol_factory: Callable[[], _ProtocolT], + program: Any, + *args: Any, + stdin: int | IO[Any] | None = -1, + stdout: int | IO[Any] | None = -1, + stderr: int | IO[Any] | None = -1, + universal_newlines: Literal[False] = False, + shell: Literal[False] = False, + bufsize: Literal[0] = 0, + encoding: None = None, + errors: None = None, + **kwargs: Any, + ) -> tuple[SubprocessTransport, _ProtocolT]: + raise NotImplementedError + + def add_reader(self, fd: Any, callback: Callable[[*_Ts], Any], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_reader(self, fd: Any) -> bool: + raise NotImplementedError + + def add_writer(self, fd: Any, callback: Callable[[*_Ts], Any], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_writer(self, fd: Any) -> bool: + raise NotImplementedError + + async def sock_recv(self, sock: socket, nbytes: int) -> bytes: + raise NotImplementedError + + async def sock_recv_into(self, sock: socket, buf: Any) -> int: + raise NotImplementedError + + async def sock_recvfrom(self, sock: socket, bufsize: int) -> tuple[bytes, Any]: + raise NotImplementedError + + async def sock_recvfrom_into(self, sock: socket, buf: Any, nbytes: int = 0) -> tuple[int, Any]: + raise NotImplementedError + + async def sock_sendall(self, sock: socket, data: Any) -> None: + raise NotImplementedError + + async def sock_sendto(self, sock: socket, data: Any, address: Any) -> int: + raise NotImplementedError + + async def sock_connect(self, sock: socket, address: Any) -> None: + raise NotImplementedError + + async def sock_accept(self, sock: socket) -> tuple[socket, Any]: + raise NotImplementedError + + async def sock_sendfile( + self, + sock: socket, + file: IO[bytes], + offset: int = 0, + count: int | None = None, + *, + fallback: bool | None = None + ) -> int: + raise NotImplementedError + + def add_signal_handler(self, sig: int, callback: Callable[[*_Ts], object], *args: *_Ts) -> None: + raise NotImplementedError + + def remove_signal_handler(self, sig: int) -> bool: + raise NotImplementedError + + def set_task_factory(self, factory: Any | None) -> None: + raise NotImplementedError + + def get_task_factory(self) -> None: + raise NotImplementedError + + def get_exception_handler(self) -> None: + raise NotImplementedError + + def set_exception_handler(self, handler: Any | None) -> None: + raise NotImplementedError + + def default_exception_handler(self, context: dict[str, Any]) -> None: + raise NotImplementedError + + def set_debug(self, enabled: bool) -> None: + raise NotImplementedError + +_future_state: ContextVar[_FutureState] = ContextVar("_future_state") +_loop = _Loop() +asyncio.set_event_loop(_loop) +_loop.running = True +asyncio.events._set_running_loop(_loop) + +def _set_future_state(future_state: _FutureState) -> None: + global _future_state + + _future_state.set(future_state) + +async def _return_result(export_index: int, borrows: int, coroutine: Any) -> None: + global _future_state + + try: + try: + result: Result[Any, Any] = Ok(await coroutine) + except Err as e: + result = e + + componentize_py_runtime.call_task_return(export_index, borrows, result) + except Exception as e: + _loop.exception = e + + assert _future_state.get().pending_count > 0 + _future_state.get().pending_count -= 1 + +def first_poll(export_index: int, borrows: int, coroutine: Any) -> int: + context = Context() + future_state = _FutureState(None, {}, [], 1) + context.run(_set_future_state, future_state) + asyncio.create_task(_return_result(export_index, borrows, coroutine), context=context) + return _poll(future_state) + +def _poll(future_state: _FutureState) -> int: + global _loop + + _loop.poll(future_state) + + if future_state.pending_count == 0: + if future_state.waitable_set is not None: + componentize_py_runtime.waitable_set_drop(future_state.waitable_set) + + return _CallbackCode.EXIT + else: + waitable_set = future_state.waitable_set + assert waitable_set is not None + componentize_py_runtime.context_set(future_state) + return _CallbackCode.WAIT | (waitable_set << 4) + +def callback(event0: int, event1: int, event2: int) -> int: + future_state = componentize_py_runtime.context_get() + componentize_py_runtime.context_set(None) + + match event0: + case _Event.NONE: + pass + case _Event.SUBTASK: + match event2: + case _Status.STARTING: + raise AssertionError + case _Status.STARTED: + pass + case _Status.RETURNED: + componentize_py_runtime.waitable_join(event1, 0) + componentize_py_runtime.subtask_drop(event1) + future_state.futures.pop(event1).set_result(event2) + case _: + # todo + raise NotImplementedError + case _Event.STREAM_READ | _Event.STREAM_WRITE | _Event.FUTURE_READ | _Event.FUTURE_WRITE: + componentize_py_runtime.waitable_join(event1, 0) + future_state.futures.pop(event1).set_result(event2) + case _: + # todo + raise NotImplementedError + + return _poll(future_state) + +async def await_result[T](result: Result[T, tuple[int, int]]) -> T: + global _loop + global _future_state + + if isinstance(result, Ok): + return result.value + else: + future_state = _future_state.get() + waitable, promise = result.value + future = _loop.create_future() + future_state.futures[waitable] = future + + if future_state.waitable_set is None: + future_state.waitable_set = componentize_py_runtime.waitable_set_new() + componentize_py_runtime.waitable_join(waitable, future_state.waitable_set) + + return cast(T, componentize_py_runtime.promise_get_result(await future, promise)) + +async def _wrap_spawned(coroutine: Any) -> None: + global _future_state + + try: + await coroutine + except Exception as e: + _loop.exception = e + + assert _future_state.get().pending_count > 0 + _future_state.get().pending_count -= 1 + +def spawn(coroutine: Any) -> None: + global _future_state + + _future_state.get().pending_count += 1 + + asyncio.create_task(_wrap_spawned(coroutine)) diff --git a/src/pumpkin_api/wit_files/componentize_py_async_support/futures.py b/src/pumpkin_api/wit_files/componentize_py_async_support/futures.py new file mode 100644 index 0000000..a50c16e --- /dev/null +++ b/src/pumpkin_api/wit_files/componentize_py_async_support/futures.py @@ -0,0 +1,93 @@ +import componentize_py_runtime +import componentize_py_async_support +import weakref + +from typing import TypeVar, Generic, cast, Self, Any, Callable +from types import TracebackType +from componentize_py_async_support import _ReturnCode + +T = TypeVar('T') + +class FutureReader(Generic[T]): + def __init__(self, type_: int, handle: int): + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.future_drop_readable, type_, handle) + + async def read(self) -> T: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + result = await componentize_py_async_support.await_result( + componentize_py_runtime.future_read(self.type_, handle) + ) + componentize_py_runtime.future_drop_readable(self.type_, handle) + return cast(T, result) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + if self.handle is not None: + self.finalizer.detach() + handle = self.handle + self.handle = None + componentize_py_runtime.future_drop_readable(self.type_, handle) + + return None + +async def write(type_: int, handle: int, value: Any) -> None: + await componentize_py_async_support.await_result( + componentize_py_runtime.future_write(type_, handle, value) + ) + componentize_py_runtime.future_drop_writable(type_, handle) + +def write_default(type_: int, handle: int, default: Callable[[], Any]) -> None: + componentize_py_async_support.spawn(write(type_, handle, default())) + +class FutureWriter(Generic[T]): + def __init__(self, type_: int, handle: int, default: Callable[[], T]): + self.type_ = type_ + self.handle: int | None = handle + self.default = default + self.finalizer = weakref.finalize(self, write_default, type_, handle, default) + + async def write(self, value: T) -> bool: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + code, _ = await componentize_py_async_support.await_result( + componentize_py_runtime.future_write(self.type_, handle, value) + ) + componentize_py_runtime.future_drop_writable(self.type_, handle) + match code: + case _ReturnCode.COMPLETED: + return True + case _ReturnCode.DROPPED: + return False + case _ReturnCode.CANCELLED: + # todo + raise NotImplementedError + case _: + raise AssertionError + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + if self.handle is not None: + componentize_py_async_support.spawn(self.write(self.default())) + + return None diff --git a/src/pumpkin_api/wit_files/componentize_py_async_support/streams.py b/src/pumpkin_api/wit_files/componentize_py_async_support/streams.py new file mode 100644 index 0000000..7650d62 --- /dev/null +++ b/src/pumpkin_api/wit_files/componentize_py_async_support/streams.py @@ -0,0 +1,189 @@ +import componentize_py_runtime +import componentize_py_async_support +import weakref + +from typing import TypeVar, Generic, Self, cast +from types import TracebackType +from componentize_py_async_support import _ReturnCode + +class ByteStreamReader: + def __init__(self, type_: int, handle: int): + self.writer_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) + + async def read(self, max_count: int) -> bytes: + if self.writer_dropped: + return bytes() + + code, values = await self._read(max_count) + + if code == _ReturnCode.DROPPED: + self.writer_dropped = True + + return values + + async def _read(self, max_count: int) -> tuple[int, bytes]: + if self.handle is not None: + return cast(tuple[int, bytes], await componentize_py_async_support.await_result( + componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + )) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_readable(self.type_, handle) + return None + +class ByteStreamWriter: + def __init__(self, type_: int, handle: int): + self.reader_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) + + async def write(self, source: bytes) -> int: + if self.reader_dropped: + return 0 + + code, count = await self._write(source) + + if code == _ReturnCode.DROPPED: + self.reader_dropped = True + + return count + + async def _write(self, source: bytes) -> tuple[int, int]: + if self.handle is not None: + return await componentize_py_async_support.await_result( + componentize_py_runtime.stream_write(self.type_, self.handle, source) + ) + else: + raise AssertionError + + async def write_all(self, source: bytes) -> int: + total = 0 + + while len(source) > 0 and not self.reader_dropped: + count = await self.write(source) + source = source[count:] + total += count + + return total + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_writable(self.type_, handle) + return None + +T = TypeVar('T') + +class StreamReader(Generic[T]): + def __init__(self, type_: int, handle: int): + self.writer_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) + + async def read(self, max_count: int) -> list[T]: + if self.writer_dropped: + return [] + + code, values = await self._read(max_count) + + if code == _ReturnCode.DROPPED: + self.writer_dropped = True + + return values + + async def _read(self, max_count: int) -> tuple[int, list[T]]: + if self.handle is not None: + return cast(tuple[int, list[T]], await componentize_py_async_support.await_result( + componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + )) + else: + raise AssertionError + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_readable(self.type_, handle) + return None + +class StreamWriter(Generic[T]): + def __init__(self, type_: int, handle: int): + self.reader_dropped = False + self.type_ = type_ + self.handle: int | None = handle + self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) + + async def write(self, source: list[T]) -> int: + if self.reader_dropped: + return 0 + + code, count = await self._write(source) + + if code == _ReturnCode.DROPPED: + self.reader_dropped = True + + return count + + async def _write(self, source: list[T]) -> tuple[int, int]: + if self.handle is not None: + return await componentize_py_async_support.await_result( + componentize_py_runtime.stream_write(self.type_, self.handle, source) + ) + else: + raise AssertionError + + async def write_all(self, source: list[T]) -> int: + total = 0 + + while len(source) > 0 and not self.reader_dropped: + count = await self.write(source) + source = source[count:] + total += count + + return total + + def __enter__(self) -> Self: + return self + + def __exit__(self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None) -> bool | None: + self.finalizer.detach() + handle = self.handle + self.handle = None + if handle is not None: + componentize_py_runtime.stream_drop_writable(self.type_, handle) + return None diff --git a/src/pumpkin_api/wit_files/componentize_py_runtime.pyi b/src/pumpkin_api/wit_files/componentize_py_runtime.pyi new file mode 100644 index 0000000..ff09cf8 --- /dev/null +++ b/src/pumpkin_api/wit_files/componentize_py_runtime.pyi @@ -0,0 +1,34 @@ +from typing import Any +from componentize_py_types import Result + +def call_task_return(index: int, borrows: int, result: Any) -> None: ... + +def waitable_set_drop(set: int) -> None: ... + +def context_set(value: Any) -> None: ... + +def context_get() -> Any: ... + +def waitable_join(waitable: int, set: int) -> None: ... + +def subtask_drop(task: int) -> None: ... + +def waitable_set_new() -> int: ... + +def promise_get_result(event: int, promise: int) -> Any: ... + +def future_read(ty: int, future: int) -> Result[Any, tuple[int, int]]: ... + +def future_write(ty: int, future: int, value: Any) -> Result[tuple[int, int], tuple[int, int]]: ... + +def future_drop_readable(ty: int, future: int) -> None: ... + +def future_drop_writable(ty: int, future: int) -> None: ... + +def stream_read(ty: int, stream: int, max_count: int) -> Result[tuple[int, bytes | list[Any]], tuple[int, int]]: ... + +def stream_write(ty: int, stream: int, values: bytes | list[Any]) -> Result[tuple[int, int], tuple[int, int]]: ... + +def stream_drop_readable(ty: int, stream: int) -> None: ... + +def stream_drop_writable(ty: int, stream: int) -> None: ... diff --git a/src/pumpkin_api/wit_files/componentize_py_types.py b/src/pumpkin_api/wit_files/componentize_py_types.py new file mode 100644 index 0000000..9867d14 --- /dev/null +++ b/src/pumpkin_api/wit_files/componentize_py_types.py @@ -0,0 +1,19 @@ +from typing import TypeVar, Generic, Union +from dataclasses import dataclass + +S = TypeVar('S') +@dataclass +class Some(Generic[S]): + value: S + +T = TypeVar('T') +@dataclass +class Ok(Generic[T]): + value: T + +E = TypeVar('E') +@dataclass(frozen=True) +class Err(Generic[E], Exception): + value: E + +Result = Union[Ok[T], Err[E]] diff --git a/src/pumpkin_api/wit_files/context.wit b/src/pumpkin_api/wit_files/context.wit new file mode 100644 index 0000000..d702de6 --- /dev/null +++ b/src/pumpkin_api/wit_files/context.wit @@ -0,0 +1,33 @@ +/// The core context provided to a plugin during initialization and event handling. +interface context { + use server.{server}; + use event.{event-type, event-priority}; + use command.{command}; + use permission.{permission}; + + /// A contextual handle for plugin-related operations. + resource context { + /// Registers a handler for a specific event. + /// + /// * `handler-id`: Unique ID for the event handler. + /// * `event-type`: The type of event to listen for. + /// * `event-priority`: When this handler should be called relative to others. + /// * `blocking`: Whether the event should wait for this handler to finish. + register-event: func(handler-id: u32, event-type: event-type, event-priority: event-priority, blocking: bool); + + /// Registers a new command. + /// + /// * `command`: The command definition. + /// * `permission`: The required permission node to execute this command. + register-command: func(command: command, permission: string); + + /// Registers a permission node with the server. + register-permission: func(permission: permission) -> result<_, string>; + + /// Returns the path to the plugin's private data folder. + get-data-folder: func() -> string; + + /// Returns the global server instance. + get-server: func() -> server; + } +} diff --git a/src/pumpkin_api/wit_files/entity.wit b/src/pumpkin_api/wit_files/entity.wit new file mode 100644 index 0000000..5f39534 --- /dev/null +++ b/src/pumpkin_api/wit_files/entity.wit @@ -0,0 +1,36 @@ +interface entity { + use common.{block-position}; + + resource block-entity { + resource-location: func() -> string; + get-position: func() -> block-position; + get-id: func() -> u32; + // write-nbt: func(nbt: nbt-compound); + // from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; + // tick: func(world: simple-world); + // write-internal: func(nbt: nbt-compound); + // chunk-data-nbt: func() -> option option; + // set-block-state: func(block-state: block-state-id); + // on-block-replaced: func(world: simple-world, position: block-position); + is-dirty: func() -> bool; + clear-dirty: func(); + // to-property-delegate: func() -> option; + // to-experience-container: func() -> option; + } + + resource command-block-entity { + get-block-entity: func () -> block-entity; + last-output: func() -> string; + track-output: func() -> bool; + success-count: func() -> u32; + command: func() -> string; + auto: func() -> bool; + condition-met: func() -> bool; + powered: func() -> bool; + } + + variant entity { + command-block-entity(command-block-entity), + } +} diff --git a/src/pumpkin_api/wit_files/event.wit b/src/pumpkin_api/wit_files/event.wit new file mode 100644 index 0000000..1d3a91d --- /dev/null +++ b/src/pumpkin_api/wit_files/event.wit @@ -0,0 +1,311 @@ +interface event { + use common.{block-position, game-mode, hand, item-stack, position}; + use player.{player}; + use text.{text-component}; + use %world.{%world}; + + enum event-priority { + highest, + high, + normal, + low, + lowest, + } + + record player-join-event-data { + player: player, + join-message: text-component, + cancelled: bool + } + + record player-leave-event-data { + player: player, + leave-message: text-component, + cancelled: bool + } + + record player-login-event-data { + player: player, + kick-message: text-component, + cancelled: bool + } + + record player-chat-event-data { + player: player, + message: string, + recipients: list, + cancelled: bool + } + + record player-command-send-event-data { + player: player, + command: string, + cancelled: bool + } + + record player-permission-check-event-data { + player: player, + permission: string, + permission-result: bool + } + + record player-move-event-data { + player: player, + from-position: position, + to-position: position, + cancelled: bool + } + + record player-teleport-event-data { + player: player, + from-position: position, + to-position: position, + cancelled: bool + } + + record player-change-world-event-data { + player: player, + previous-world: %world, + new-world: %world, + position: position, + yaw: f32, + pitch: f32, + cancelled: bool + } + + record player-exp-change-event-data { + player: player, + amount: s32 + } + + record player-item-held-event-data { + player: player, + previous-slot: u8, + new-slot: u8, + cancelled: bool + } + + record player-changed-main-hand-event-data { + player: player, + main-hand: hand + } + + record player-gamemode-change-event-data { + player: player, + previous-gamemode: game-mode, + new-gamemode: game-mode, + cancelled: bool + } + + record player-custom-payload-event-data { + player: player, + channel: string, + data: list + } + + enum player-fish-state { + fishing, + caught-fish, + caught-entity, + in-ground, + failed-attempt, + reel-in, + bite, + } + + record player-fish-event-data { + player: player, + caught-uuid: option, + caught-type: string, + hook-uuid: string, + state: player-fish-state, + hand: hand, + exp-to-drop: s32, + cancelled: bool + } + + record player-egg-throw-event-data { + player: player, + egg-uuid: string, + hatching: bool, + num-hatches: u8, + hatching-type: string, + cancelled: bool + } + + enum entity-interaction-action { + interact, + attack, + interact-at, + } + + enum interact-action { + left-click-block, + left-click-air, + right-click-air, + right-click-block, + } + + record player-interact-event-data { + player: player, + action: interact-action, + clicked-pos: option, + item: item-stack, + block: string, + cancelled: bool + } + + record player-toggle-sneak-event-data { + player: player, + is-sneaking: bool, + cancelled: bool + } + + record player-toggle-sprint-event-data { + player: player, + is-sprinting: bool, + cancelled: bool + } + + record player-interact-unknown-entity-event-data { + player: player, + entity-id: s32, + action: entity-interaction-action, + cancelled: bool + } + + record block-redstone-event-data { + target-world: %world, + state-id: u16, + block-position: block-position, + old-current: s32, + new-current: s32, + cancelled: bool + } + + record block-break-event-data { + player: option, + block: string, + block-position: block-position, + exp: u32, + should-drop: bool, + cancelled: bool + } + + record block-burn-event-data { + igniting-block: string, + block: string, + cancelled: bool + } + + record block-can-build-event-data { + block-to-build: string, + buildable: bool, + player: player, + block: string, + cancelled: bool + } + + record block-grow-event-data { + target-world: %world, + old-block: string, + old-state-id: u16, + new-block: string, + new-state-id: u16, + block-position: block-position, + cancelled: bool + } + + record block-place-event-data { + player: player, + block-placed: string, + block-placed-against: string, + block-position: block-position, + can-build: bool, + cancelled: bool + } + record server-command-event-data { + command: string, + cancelled: bool + } + + record spawn-change-event-data { + target-world: %world, + previous-position: block-position, + previous-yaw: f32, + previous-pitch: f32, + new-position: block-position, + new-yaw: f32, + new-pitch: f32 + } + + record server-broadcast-event-data { + message: text-component, + sender: text-component, + cancelled: bool + } + + enum event-type { + player-join-event, + player-leave-event, + player-login-event, + player-chat-event, + player-command-send-event, + player-permission-check-event, + player-move-event, + player-teleport-event, + player-change-world-event, + player-exp-change-event, + player-item-held-event, + player-changed-main-hand-event, + player-gamemode-change-event, + player-custom-payload-event, + player-fish-event, + player-egg-throw-event, + player-interact-unknown-entity-event, + player-interact-event, + player-toggle-sneak-event, + player-toggle-sprint-event, + block-redstone-event, + block-break-event, + block-burn-event, + block-can-build-event, + block-grow-event, + block-place-event, + server-command-event, + spawn-change-event, + server-broadcast-event, + } + + variant event { + player-join-event(player-join-event-data), + player-leave-event(player-leave-event-data), + player-login-event(player-login-event-data), + player-chat-event(player-chat-event-data), + player-command-send-event(player-command-send-event-data), + player-permission-check-event(player-permission-check-event-data), + player-move-event(player-move-event-data), + player-teleport-event(player-teleport-event-data), + player-change-world-event(player-change-world-event-data), + player-exp-change-event(player-exp-change-event-data), + player-item-held-event(player-item-held-event-data), + player-changed-main-hand-event(player-changed-main-hand-event-data), + player-gamemode-change-event(player-gamemode-change-event-data), + player-custom-payload-event(player-custom-payload-event-data), + player-fish-event(player-fish-event-data), + player-egg-throw-event(player-egg-throw-event-data), + player-interact-unknown-entity-event(player-interact-unknown-entity-event-data), + player-interact-event(player-interact-event-data), + player-toggle-sneak-event(player-toggle-sneak-event-data), + player-toggle-sprint-event(player-toggle-sprint-event-data), + block-redstone-event(block-redstone-event-data), + block-break-event(block-break-event-data), + block-burn-event(block-burn-event-data), + block-can-build-event(block-can-build-event-data), + block-grow-event(block-grow-event-data), + block-place-event(block-place-event-data), + server-command-event(server-command-event-data), + spawn-change-event(spawn-change-event-data), + server-broadcast-event(server-broadcast-event-data), + } +} diff --git a/src/pumpkin_api/wit_files/gui.wit b/src/pumpkin_api/wit_files/gui.wit new file mode 100644 index 0000000..4f57442 --- /dev/null +++ b/src/pumpkin_api/wit_files/gui.wit @@ -0,0 +1,38 @@ +interface gui { + use text.{text-component}; + use common.{item-stack}; + + enum gui-type { + generic-9x1, + generic-9x2, + generic-9x3, + generic-9x4, + generic-9x5, + generic-9x6, + generic-3x3, + crafter-3x3, + anvil, + beacon, + blast-furnace, + brewing-stand, + crafting, + enchantment, + furnace, + grindstone, + hopper, + lectern, + loom, + merchant, + shulker-box, + smithing, + smoker, + cartography-table, + stonecutter, + } + + resource gui { + constructor(%type: gui-type, title: text-component); + set-item: func(slot: u32, item: item-stack); + get-item: func(slot: u32) -> option; + } +} diff --git a/src/pumpkin_api/wit_files/i18n.wit b/src/pumpkin_api/wit_files/i18n.wit new file mode 100644 index 0000000..2d75074 --- /dev/null +++ b/src/pumpkin_api/wit_files/i18n.wit @@ -0,0 +1,21 @@ +interface i18n { + use common.{locale}; + + /// Retrieves a translated string from the host. + /// + /// # Arguments + /// * `key`: The namespaced translation key (e.g. `pumpkin:my_key`). + /// * `locale`: The target locale. + /// + /// # Returns + /// The localized string, falling back to English or the key itself. + translate: func(key: string, locale: locale) -> string; + + /// Loads custom translations from a JSON string. + /// + /// # Arguments + /// * `namespace`: The namespace to associate these keys with. + /// * `json`: A JSON string containing a flat map of translations. + /// * `locale`: The target locale for these translations. + load-translations: func(namespace: string, json: string, locale: locale); +} diff --git a/src/pumpkin_api/wit_files/log.wit b/src/pumpkin_api/wit_files/log.wit new file mode 100644 index 0000000..aafd268 --- /dev/null +++ b/src/pumpkin_api/wit_files/log.wit @@ -0,0 +1,14 @@ +interface logging { + enum level { + trace, + debug, + info, + warn, + error, + } + + /// log any general purpose message + log: func(level: level, message: string); + /// This function is meant to be used by the tracing crate. + log-tracing: func(event: list); +} diff --git a/src/pumpkin_api/wit_files/metadata.wit b/src/pumpkin_api/wit_files/metadata.wit new file mode 100644 index 0000000..f13f421 --- /dev/null +++ b/src/pumpkin_api/wit_files/metadata.wit @@ -0,0 +1,15 @@ +/// Plugin metadata describing the plugin and its compatibility. +interface metadata { + record plugin-metadata { + /// Name of the plugin. + name: string, + /// Plugin version (semver). + version: string, + /// Plugin authors. + authors: list, + /// Short description of the plugin. + description: string, + } + + get-metadata: func() -> plugin-metadata; +} diff --git a/src/pumpkin_api/wit_files/permission.wit b/src/pumpkin_api/wit_files/permission.wit new file mode 100644 index 0000000..3ad4415 --- /dev/null +++ b/src/pumpkin_api/wit_files/permission.wit @@ -0,0 +1,46 @@ +interface permission { + /// Represents the player's permission level + /// + /// Permission levels determine the player's access to commands and server operations. + /// Each numeric level corresponds to a specific role: + /// - `Zero`: `normal`: Player can use basic commands. + /// - `One`: `moderator`: Player can bypass spawn protection. + /// - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks. + /// - `Three`: `admin`: Player or executor can use commands related to multiplayer management. + /// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. + enum permission-level { + zero, + one, + two, + three, + four, + } + + /// Describes the default behavior for permissions + variant permission-default { + /// Permission is not granted by default + deny, + /// Permission is granted by default + allow, + /// Permission is granted by default to operators + op(permission-level), + } + + /// A child permission entry + record permission-child { + node: string, + value: bool, + } + + /// Defines a permission node in the system + record permission { + /// The full node name (e.g., "minecraft:command.gamemode") + node: string, + /// Description of what this permission does + description: string, + /// The default value of this permission + default: permission-default, + /// Children nodes that are affected by this permission + children: list, + } +} diff --git a/src/pumpkin_api/wit_files/player.wit b/src/pumpkin_api/wit_files/player.wit new file mode 100644 index 0000000..48385d5 --- /dev/null +++ b/src/pumpkin_api/wit_files/player.wit @@ -0,0 +1,172 @@ +/// Represents a player connected to the Minecraft server. +interface player { + use common.{hand, item-stack, position, game-mode}; + use %world.{%world}; + use permission.{permission-level}; + use text.{text-component}; + use gui.{gui}; + + /// Various abilities a player can have. + record player-abilities { + /// Whether the player is invulnerable to damage. + invulnerable: bool, + /// Whether the player is currently flying. + flying: bool, + /// Whether the player is allowed to fly. + allow-flying: bool, + /// Whether the player is in creative mode. + creative: bool, + /// Whether the player can modify the world. + allow-modify-world: bool, + /// The speed at which the player flies. + fly-speed: f32, + /// The speed at which the player walks. + walk-speed: f32, + } + + /// A handle to a player on the server. + resource player { + /// Returns the player's unique UUID as a string. + get-id: func () -> string; + /// Returns the player's username. + get-name: func () -> string; + + // Basic state + + /// Returns the player's current position in the world. + get-position: func () -> position; + /// Returns the player's current yaw (horizontal rotation). + get-yaw: func () -> f32; + /// Returns the player's current pitch (vertical rotation). + get-pitch: func () -> f32; + /// Returns the world the player is currently in. + get-world: func () -> %world; + /// Returns the player's current game mode. + get-gamemode: func () -> game-mode; + /// Changes the player's game mode. Returns true if successful. + set-gamemode: func (mode: game-mode) -> bool; + /// Returns the player's locale (e.g., "en_us"). + get-locale: func () -> string; + /// Returns the player's current ping in milliseconds. + get-ping: func () -> u32; + + // Permissions & metadata + + /// Returns the player's permission level (0-4). + get-permission-level: func () -> permission-level; + /// Sets the player's permission level. + set-permission-level: func (level: permission-level); + /// Checks if the player has a specific permission node. + has-permission: func (node: string) -> bool; + /// Returns the player's display name, which may include formatting. + get-display-name: func () -> text-component; + + // Messaging & titles + + /// Sends a system message to the player's chat. + send-system-message: func (text: text-component, overlay: bool); + /// Shows a large title on the player's screen. + show-title: func (text: text-component); + /// Shows a smaller subtitle on the player's screen. + show-subtitle: func (text: text-component); + /// Shows a message in the action bar area. + show-actionbar: func (text: text-component); + /// Sets the fade-in, stay, and fade-out times for titles. + send-title-animation: func (fade-in: s32, stay: s32, fade-out: s32); + + // Teleport & kick + + /// Teleports the player to a position in a world. + teleport: func (position: position, yaw: option, pitch: option, %world: %world); + /// Teleports the player to another world at a specific position. + teleport-world: func (world-ref: %world, position: position, yaw: option, pitch: option); + /// Kicks the player from the server with the specified reason. + kick: func (message: text-component); + /// Forces the player to respawn. + respawn: func (); + + /// Opens a GUI for the player. + open-gui: func (gui-ref: gui); + + /// Bans the player with an optional reason. + ban: func (reason: option); + /// Bans the player's IP address with an optional reason. + ban-ip: func (reason: option); + + // Inventory + + /// Returns the index of the currently selected hotbar slot (0-8). + get-selected-slot: func () -> u8; + /// Returns the item currently in the specified hand. + get-item-in-hand: func (hand: hand) -> option; + /// Returns the item in the specified inventory slot. + get-inventory-item: func (slot: u8) -> option; + + // Status + + /// Returns the player's current health. + get-health: func () -> f32; + /// Sets the player's current health. + set-health: func (health: f32); + /// Returns the player's maximum health. + get-max-health: func () -> f32; + /// Sets the player's maximum health. + set-max-health: func (max-health: f32); + /// Returns the player's food level (0-20). + get-food-level: func () -> u8; + /// Sets the player's food level. + set-food-level: func (food-level: u8); + /// Returns the player's food saturation level. + get-saturation: func () -> f32; + /// Sets the player's food saturation level. + set-saturation: func (saturation: f32); + /// Returns the player's food exhaustion level. + get-exhaustion: func () -> f32; + /// Sets the player's food exhaustion level. + set-exhaustion: func (exhaustion: f32); + /// Returns the player's current absorption amount. + get-absorption: func () -> f32; + /// Sets the player's current absorption amount. + set-absorption: func (absorption: f32); + + /// Returns the player's total experience level. + get-experience-level: func() -> s32; + /// Returns the player's progress toward the next level (0.0 to 1.0). + get-experience-progress: func() -> f32; + /// Returns the total experience points the player has. + get-experience-points: func() -> s32; + /// Sets the player's total experience level. + set-experience-level: func(level: s32); + /// Sets the player's progress toward the next level. + set-experience-progress: func(progress: f32); + /// Sets the player's total experience points. + set-experience-points: func(points: s32); + /// Adds experience levels to the player. + add-experience-levels: func(levels: s32); + /// Adds experience points to the player. + add-experience-points: func(points: s32); + + /// Returns whether the player is currently sneaking. + is-sneaking: func() -> bool; + /// Sets whether the player is currently sneaking. + set-sneaking: func(sneaking: bool); + /// Returns whether the player is currently sprinting. + is-sprinting: func() -> bool; + /// Sets whether the player is currently sprinting. + set-sprinting: func(sprinting: bool); + /// Returns whether the player is currently on the ground. + is-on-ground: func() -> bool; + /// Returns whether the player is currently flying. + is-flying: func() -> bool; + /// Sets whether the player should be flying. + set-flying: func(flying: bool); + + /// Returns the player's current abilities. + get-abilities: func() -> player-abilities; + /// Updates the player's abilities. + set-abilities: func(abilities: player-abilities); + + /// Returns the player's IP address. + get-ip: func() -> string; + } +} diff --git a/src/pumpkin_api/wit_files/plugin.wit b/src/pumpkin_api/wit_files/plugin.wit new file mode 100644 index 0000000..dd6a272 --- /dev/null +++ b/src/pumpkin_api/wit_files/plugin.wit @@ -0,0 +1,61 @@ +/// The main entry point for a Pumpkin plugin. +/// This world defines the exports that the plugin must provide to the host +/// and the imports that the host provides to the plugin. +package pumpkin:plugin; + +world plugin { + use context.{context}; + use event.{event}; + use server.{server as server-instance}; + use command.{command-sender, consumed-args, command-error}; + + // --- Host-provided imports --- + + /// Provides logging capabilities to the plugin. + import logging; + /// Provides GUI creation and management. + import gui; + /// Provides scoreboard management. + import scoreboard; + /// Provides global server information and player lookup. + import server; + /// Provides text component building and formatting. + import text; + /// Provides command registration and argument parsing. + import command; + /// Provides the plugin execution context. + import context; + /// Provides internationalization and translation support. + import i18n; + /// Provides task scheduling for delayed or repeating logic. + import scheduler; + + // --- Plugin-provided exports --- + + /// Called by the host once to initialize the plugin. + /// Internal use only; plugins should use the `register_plugin!` macro. + export init-plugin: func(); + + /// Called when the plugin is being loaded. + /// Use this to register commands, events, and perform initial setup. + export on-load: func(context: context) -> result<_, string>; + + /// Called when the plugin is being unloaded. + /// Use this to save data and clean up resources. + export on-unload: func(context: context) -> result<_, string>; + + /// Provides plugin metadata (name, version, etc.) to the host. + export metadata; + + /// Common types used across the API. + export common; + + /// Dispatches an event to the plugin for handling. + export handle-event: func(event-id: u32, server: server-instance, event: event) -> event; + + /// Dispatches a command execution to the plugin. + export handle-command: func(command-id: u32, sender: command-sender, server: server-instance, args: consumed-args) -> result; + + /// Dispatches a scheduled task execution to the plugin. + export handle-task: func(handler-id: u32, server: server-instance); +} diff --git a/src/pumpkin_api/wit_files/poll_loop.py b/src/pumpkin_api/wit_files/poll_loop.py new file mode 100644 index 0000000..504c958 --- /dev/null +++ b/src/pumpkin_api/wit_files/poll_loop.py @@ -0,0 +1,440 @@ +"""Defines a custom `asyncio` event loop backed by `wasi:io/poll#poll`. + +This also includes helper classes and functions for working with `wasi:http`. + +This is only useful for `wasi:http@0.2.x`; `wasi:http@0.3.x` uses a different +mechanism to model concurrency. +""" + +import asyncio +import socket +import subprocess + +from componentize_py_types import Ok, Err +from wit_world.imports import types, streams, poll, outgoing_handler +from wit_world.imports.types import ( + IncomingBody, + OutgoingBody, + OutgoingRequest, + IncomingResponse, +) +from wit_world.imports.streams import StreamError_Closed, InputStream +from wit_world.imports.poll import Pollable +from typing import Optional, cast + +# Maximum number of bytes to read at a time +READ_SIZE: int = 16 * 1024 + + +async def send(request: OutgoingRequest) -> IncomingResponse: + """Send the specified request and wait asynchronously for the response.""" + + future = outgoing_handler.handle(request, None) + + while True: + response = future.get() + if response is None: + await register(cast(PollLoop, asyncio.get_event_loop()), future.subscribe()) + else: + if isinstance(response, Ok): + if isinstance(response.value, Ok): + return response.value.value + else: + raise response.value + else: + raise response + + +class Stream: + """Reader abstraction over `wasi:http/types#incoming-body`.""" + + def __init__(self, body: IncomingBody): + self.body: Optional[IncomingBody] = body + self.stream: Optional[InputStream] = body.stream() + + async def next(self) -> Optional[bytes]: + """Wait for the next chunk of data to arrive on the stream. + + This will return `None` when the end of the stream has been reached. + """ + while True: + try: + if self.stream is None: + return None + else: + buffer = self.stream.read(READ_SIZE) + if len(buffer) == 0: + await register( + cast(PollLoop, asyncio.get_event_loop()), + self.stream.subscribe(), + ) + else: + return buffer + except Err as e: + if isinstance(e.value, StreamError_Closed): + if self.stream is not None: + self.stream.__exit__(None, None, None) + self.stream = None + if self.body is not None: + IncomingBody.finish(self.body) + self.body = None + else: + raise e + + +class Sink: + """Writer abstraction over `wasi:http/types#outgoing-body`.""" + + def __init__(self, body: OutgoingBody): + self.body = body + self.stream = body.write() + + async def send(self, chunk: bytes): + """Write the specified bytes to the sink. + + This may need to yield according to the backpressure requirements of the sink. + """ + offset = 0 + flushing = False + while True: + count = self.stream.check_write() + if count == 0: + await register( + cast(PollLoop, asyncio.get_event_loop()), self.stream.subscribe() + ) + elif offset == len(chunk): + if flushing: + return + else: + self.stream.flush() + flushing = True + else: + count = min(count, len(chunk) - offset) + self.stream.write(chunk[offset : offset + count]) + offset += count + + def close(self): + """Close the stream, indicating no further data will be written.""" + + self.stream.__exit__(None, None, None) + self.stream = None + OutgoingBody.finish(self.body, None) + self.body = None + + +class PollLoop(asyncio.AbstractEventLoop): + """Custom `asyncio` event loop backed by `wasi:io/poll#poll`.""" + + def __init__(self): + self.wakers = [] + self.running = False + self.handles = [] + self.exception = None + + def get_debug(self): + return False + + def run_until_complete(self, future): + future = asyncio.ensure_future(future, loop=self) + + self.running = True + asyncio.events._set_running_loop(self) + while self.running and not future.done(): + handles = self.handles + self.handles = [] + for handle in handles: + if not handle._cancelled: + handle._run() + + if self.wakers: + [pollables, wakers] = list(map(list, zip(*self.wakers))) + + new_wakers = [] + ready = [False] * len(pollables) + for index in poll.poll(pollables): + ready[index] = True + + for (ready, pollable), waker in zip(zip(ready, pollables), wakers): + if ready: + pollable.__exit__(None, None, None) + waker.set_result(None) + else: + new_wakers.append((pollable, waker)) + + self.wakers = new_wakers + + if self.exception is not None: + raise self.exception + + return future.result() + + def is_running(self): + return self.running + + def is_closed(self): + return not self.running + + def stop(self): + self.running = False + + def close(self): + self.running = False + + def shutdown_asyncgens(self): + pass + + def call_exception_handler(self, context): + self.exception = context.get("exception", None) + + def call_soon(self, callback, *args, context=None): + handle = asyncio.Handle(callback, args, self, context) + self.handles.append(handle) + return handle + + def create_task(self, coroutine): + return asyncio.Task(coroutine, loop=self) + + def create_future(self): + return asyncio.Future(loop=self) + + # The remaining methods should be irrelevant for our purposes and thus unimplemented + + def run_forever(self): + raise NotImplementedError + + async def shutdown_default_executor(self): + raise NotImplementedError + + def _timer_handle_cancelled(self, handle): + raise NotImplementedError + + def call_later(self, delay, callback, *args, context=None): + raise NotImplementedError + + def call_at(self, when, callback, *args, context=None): + raise NotImplementedError + + def time(self): + raise NotImplementedError + + def call_soon_threadsafe(self, callback, *args, context=None): + raise NotImplementedError + + def run_in_executor(self, executor, func, *args): + raise NotImplementedError + + def set_default_executor(self, executor): + raise NotImplementedError + + async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): + raise NotImplementedError + + async def getnameinfo(self, sockaddr, flags=0): + raise NotImplementedError + + async def create_connection( + self, + protocol_factory, + host=None, + port=None, + *, + ssl=None, + family=0, + proto=0, + flags=0, + sock=None, + local_addr=None, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + happy_eyeballs_delay=None, + interleave=None, + ): + raise NotImplementedError + + async def create_server( + self, + protocol_factory, + host=None, + port=None, + *, + family=socket.AF_UNSPEC, + flags=socket.AI_PASSIVE, + sock=None, + backlog=100, + ssl=None, + reuse_address=None, + reuse_port=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + start_serving=True, + ): + raise NotImplementedError + + async def sendfile(self, transport, file, offset=0, count=None, *, fallback=True): + raise NotImplementedError + + async def start_tls( + self, + transport, + protocol, + sslcontext, + *, + server_side=False, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_unix_connection( + self, + protocol_factory, + path=None, + *, + ssl=None, + sock=None, + server_hostname=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_unix_server( + self, + protocol_factory, + path=None, + *, + sock=None, + backlog=100, + ssl=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + start_serving=True, + ): + raise NotImplementedError + + async def connect_accepted_socket( + self, + protocol_factory, + sock, + *, + ssl=None, + ssl_handshake_timeout=None, + ssl_shutdown_timeout=None, + ): + raise NotImplementedError + + async def create_datagram_endpoint( + self, + protocol_factory, + local_addr=None, + remote_addr=None, + *, + family=0, + proto=0, + flags=0, + reuse_address=None, + reuse_port=None, + allow_broadcast=None, + sock=None, + ): + raise NotImplementedError + + async def connect_read_pipe(self, protocol_factory, pipe): + raise NotImplementedError + + async def connect_write_pipe(self, protocol_factory, pipe): + raise NotImplementedError + + async def subprocess_shell( + self, + protocol_factory, + cmd, + *, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ): + raise NotImplementedError + + async def subprocess_exec( + self, + protocol_factory, + *args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ): + raise NotImplementedError + + def add_reader(self, fd, callback, *args): + raise NotImplementedError + + def remove_reader(self, fd): + raise NotImplementedError + + def add_writer(self, fd, callback, *args): + raise NotImplementedError + + def remove_writer(self, fd): + raise NotImplementedError + + async def sock_recv(self, sock, nbytes): + raise NotImplementedError + + async def sock_recv_into(self, sock, buf): + raise NotImplementedError + + async def sock_recvfrom(self, sock, bufsize): + raise NotImplementedError + + async def sock_recvfrom_into(self, sock, buf, nbytes=0): + raise NotImplementedError + + async def sock_sendall(self, sock, data): + raise NotImplementedError + + async def sock_sendto(self, sock, data, address): + raise NotImplementedError + + async def sock_connect(self, sock, address): + raise NotImplementedError + + async def sock_accept(self, sock): + raise NotImplementedError + + async def sock_sendfile(self, sock, file, offset=0, count=None, *, fallback=None): + raise NotImplementedError + + def add_signal_handler(self, sig, callback, *args): + raise NotImplementedError + + def remove_signal_handler(self, sig): + raise NotImplementedError + + def set_task_factory(self, factory): + raise NotImplementedError + + def get_task_factory(self): + raise NotImplementedError + + def get_exception_handler(self): + raise NotImplementedError + + def set_exception_handler(self, handler): + raise NotImplementedError + + def default_exception_handler(self, context): + raise NotImplementedError + + def set_debug(self, enabled): + raise NotImplementedError + + +async def register(loop: PollLoop, pollable: Pollable): + waker = loop.create_future() + loop.wakers.append((pollable, waker)) + await waker diff --git a/src/pumpkin_api/wit_files/scheduler.wit b/src/pumpkin_api/wit_files/scheduler.wit new file mode 100644 index 0000000..cd06e37 --- /dev/null +++ b/src/pumpkin_api/wit_files/scheduler.wit @@ -0,0 +1,24 @@ +/// Provides functionality to schedule tasks to be executed later on the server. +interface scheduler { + /// Schedules a task to be executed once after the specified number of ticks. + /// + /// * `handler-id`: The unique ID of the handler to call when the task executes. + /// * `delay-ticks`: The number of game ticks to wait before execution (20 ticks = 1 second). + /// + /// Returns a unique task ID that can be used to cancel the task. + schedule-delayed-task: func(handler-id: u32, delay-ticks: u64) -> u32; + + /// Schedules a task to be executed repeatedly. + /// + /// * `handler-id`: The unique ID of the handler to call when the task executes. + /// * `delay-ticks`: The number of game ticks to wait before the first execution. + /// * `period-ticks`: The number of game ticks between subsequent executions. + /// + /// Returns a unique task ID that can be used to cancel the task. + schedule-repeating-task: func(handler-id: u32, delay-ticks: u64, period-ticks: u64) -> u32; + + /// Cancels a scheduled task if it hasn't finished yet. + /// + /// * `task-id`: The ID of the task to cancel. + cancel-task: func(task-id: u32); +} diff --git a/src/pumpkin_api/wit_files/scoreboard.wit b/src/pumpkin_api/wit_files/scoreboard.wit new file mode 100644 index 0000000..a77f207 --- /dev/null +++ b/src/pumpkin_api/wit_files/scoreboard.wit @@ -0,0 +1,72 @@ +interface scoreboard { + use text.{text-component}; + use common.{named-color}; + + enum render-type { + integer, + hearts + } + + enum display-slot { + player-list, + sidebar, + below-name, + sidebar-team-black, + sidebar-team-dark-blue, + sidebar-team-dark-green, + sidebar-team-dark-aqua, + sidebar-team-dark-red, + sidebar-team-dark-purple, + sidebar-team-gold, + sidebar-team-gray, + sidebar-team-dark-gray, + sidebar-team-blue, + sidebar-team-green, + sidebar-team-aqua, + sidebar-team-red, + sidebar-team-light-purple, + sidebar-team-yellow, + sidebar-team-white, + } + + enum nametag-visibility { + always, + never, + hide-for-other-teams, + hide-for-own-team + } + + enum collision-rule { + always, + never, + push-other-teams, + push-own-team + } + + record team-settings { + display-name: text-component, + friendly-fire: bool, + see-friendly-invisibles: bool, + nametag-visibility: nametag-visibility, + collision-rule: collision-rule, + color: named-color, + prefix: text-component, + suffix: text-component + } + + resource scoreboard { + add-objective: func(name: string, display-name: text-component, render-type: render-type); + remove-objective: func(name: string); + set-display-slot: func(slot: display-slot, objective-name: string); + + update-score: func(entity-name: string, objective-name: string, value: s32); + remove-score: func(entity-name: string, objective-name: string); + + create-team: func(name: string, settings: team-settings); + remove-team: func(name: string); + update-team: func(name: string, settings: team-settings); + + add-player-to-team: func(team-name: string, player-name: string); + remove-player-from-team: func(team-name: string, player-name: string); + } +} diff --git a/src/pumpkin_api/wit_files/server.wit b/src/pumpkin_api/wit_files/server.wit new file mode 100644 index 0000000..f71f82b --- /dev/null +++ b/src/pumpkin_api/wit_files/server.wit @@ -0,0 +1,41 @@ +interface server { + use player.{player}; + + /// Represents the difficulty setting of a Minecraft server. + enum difficulty { + /// Hostile mobs do not spawn. Players regain health automatically. + peaceful, + /// Hostile mobs deal less damage. + easy, + /// Standard Minecraft difficulty. + normal, + /// Hostile mobs deal more damage. + hard, + } + + /// Represents the global server instance. + resource server { + /// Returns the current difficulty level of the server. + get-difficulty: func() -> difficulty; + + /// Returns the total number of players currently connected to the server. + get-player-count: func() -> u32; + + /// Returns the rolling average Milliseconds Per Tick (MSPT). + /// A value below 50.0 is required for a stable 20 TPS. + get-mspt: func() -> f64; + + /// Returns the effective Ticks Per Second (TPS). + /// Ideal value is 20.0. + get-tps: func() -> f64; + + /// Returns a list of all online players across all worlds. + get-all-players: func() -> list; + + /// Searches for a player by their exact name. + get-player-by-name: func(name: string) -> option; + + /// Searches for a player by their UUID string. + get-player-by-uuid: func(id: string) -> option; + } +} diff --git a/src/pumpkin_api/wit_files/text.wit b/src/pumpkin_api/wit_files/text.wit new file mode 100644 index 0000000..cad5d6f --- /dev/null +++ b/src/pumpkin_api/wit_files/text.wit @@ -0,0 +1,41 @@ +interface text { + use common.{named-color, rgb-color, argb-color}; + + resource text-component { + text: static func(plain: string) -> text-component; + translate: static func(key: string, %with: list) -> text-component; + + add-child: func(child: text-component); + add-text: func(text: string); + get-text: func() -> string; + encode: func() -> list; + + // Style + + color-named: func(color: named-color); + color-rgb: func(color: rgb-color); + bold: func(value: bool); + italic: func(value: bool); + underlined: func(value: bool); + strikethrough: func(value: bool); + obfuscated: func(value: bool); + /// Text inserted into chat when shift-clicked + insertion: func(text: string); + font: func(font: string); + shadow-color: func(color: argb-color); + + // Click events + + click-open-url: func(url: string); + click-run-command: func(command: string); + click-suggest-command: func(command: string); + click-copy-to-clipboard: func(text: string); + + // Hover events + + hover-show-text: func(text: text-component); + /// Item data as SNBT string + hover-show-item: func(item: string); + hover-show-entity: func(entity-type: string, id: string, name: option); + } +} diff --git a/src/pumpkin_api/wit_files/wit_world/__init__.py b/src/pumpkin_api/wit_files/wit_world/__init__.py new file mode 100644 index 0000000..58938bf --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/__init__.py @@ -0,0 +1,70 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from .imports import event +from .imports import server +from .imports import command +from .imports import context + + +class WitWorld(Protocol): + + @abstractmethod + def init_plugin(self) -> None: + """ + --- Plugin-provided exports --- + Called by the host once to initialize the plugin. + Internal use only; plugins should use the `register_plugin!` macro. + """ + raise NotImplementedError + + @abstractmethod + def on_load(self, context: context.Context) -> None: + """ + Called when the plugin is being loaded. + Use this to register commands, events, and perform initial setup. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + + @abstractmethod + def on_unload(self, context: context.Context) -> None: + """ + Called when the plugin is being unloaded. + Use this to save data and clean up resources. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + + @abstractmethod + def handle_event(self, event_id: int, server: server.Server, event: event.Event) -> event.Event: + """ + Dispatches an event to the plugin for handling. + """ + raise NotImplementedError + + @abstractmethod + def handle_command(self, command_id: int, sender: command.CommandSender, server: server.Server, args: command.ConsumedArgs) -> int: + """ + Dispatches a command execution to the plugin. + + Raises: `componentize_py_types.Err(wit_world.imports.command.CommandError)` + """ + raise NotImplementedError + + @abstractmethod + def handle_task(self, handler_id: int, server: server.Server) -> None: + """ + Dispatches a scheduled task execution to the plugin. + """ + raise NotImplementedError + diff --git a/src/pumpkin_api/wit_files/wit_world/exports/__init__.py b/src/pumpkin_api/wit_files/wit_world/exports/__init__.py new file mode 100644 index 0000000..67d2941 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/exports/__init__.py @@ -0,0 +1,19 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..exports import metadata + +class Metadata(Protocol): + + @abstractmethod + def get_metadata(self) -> metadata.PluginMetadata: + raise NotImplementedError + + diff --git a/src/pumpkin_api/wit_files/wit_world/exports/metadata.py b/src/pumpkin_api/wit_files/wit_world/exports/metadata.py new file mode 100644 index 0000000..d2b4d4a --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/exports/metadata.py @@ -0,0 +1,22 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Plugin metadata describing the plugin and its compatibility. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +@dataclass +class PluginMetadata: + name: str + version: str + authors: List[str] + description: str + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/__init__.py b/src/pumpkin_api/wit_files/wit_world/imports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pumpkin_api/wit_files/wit_world/imports/command.py b/src/pumpkin_api/wit_files/wit_world/imports/command.py new file mode 100644 index 0000000..c12bdc6 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/command.py @@ -0,0 +1,539 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import entity +from ..imports import text +from ..imports import world +from ..imports import player +from ..imports import server + +class Gamemode(Enum): + SURVIVAL = 0 + CREATIVE = 1 + ADVENTURE = 2 + SPECTATOR = 3 + +class BossbarColor(Enum): + PINK = 0 + BLUE = 1 + RED = 2 + GREEN = 3 + YELLOW = 4 + PURPLE = 5 + WHITE = 6 + +class BossbarStyle(Enum): + NO_DIVISION = 0 + NOTCHES6 = 1 + NOTCHES10 = 2 + NOTCHES12 = 3 + NOTCHES20 = 4 + +class SoundCategory(Enum): + MASTER = 0 + MUSIC = 1 + RECORDS = 2 + WEATHER = 3 + BLOCKS = 4 + HOSTILE = 5 + NEUTRAL = 6 + PLAYERS = 7 + AMBIENT = 8 + VOICE = 9 + UI = 10 + +class EntityAnchor(Enum): + FEET = 0 + EYES = 1 + + +@dataclass +class Number_Float64: + value: float + + +@dataclass +class Number_Float32: + value: float + + +@dataclass +class Number_Int32: + value: int + + +@dataclass +class Number_Int64: + value: int + + +Number = Union[Number_Float64, Number_Float32, Number_Int32, Number_Int64] + + + +@dataclass +class NotInBounds_LowerBound: + value: Tuple[Number, Number] + + +@dataclass +class NotInBounds_UpperBound: + value: Tuple[Number, Number] + + +NotInBounds = Union[NotInBounds_LowerBound, NotInBounds_UpperBound] + + + +@dataclass +class Arg_Entities: + value: List[entity.Entity] + + +@dataclass +class Arg_Entity: + value: entity.Entity + + +@dataclass +class Arg_Players: + value: List[player.Player] + + +@dataclass +class Arg_BlockPos: + value: Tuple[int, int, int] + + +@dataclass +class Arg_Pos3d: + value: Tuple[float, float, float] + + +@dataclass +class Arg_Pos2d: + value: Tuple[float, float] + + +@dataclass +class Arg_Rotation: + value: Tuple[float, bool, float, bool] + + +@dataclass +class Arg_GameMode: + value: Gamemode + + +@dataclass +class Arg_Difficulty: + value: server.Difficulty + + +@dataclass +class Arg_Item: + value: str + + +@dataclass +class Arg_ItemPredicate: + value: str + + +@dataclass +class Arg_ResourceLocation: + value: str + + +@dataclass +class Arg_Block: + value: str + + +@dataclass +class Arg_BlockPredicate: + value: str + + +@dataclass +class Arg_BossbarColor: + value: BossbarColor + + +@dataclass +class Arg_BossbarStyle: + value: BossbarStyle + + +@dataclass +class Arg_Particle: + value: str + + +@dataclass +class Arg_Msg: + value: str + + +@dataclass +class Arg_TextComponent: + value: text.TextComponent + + +@dataclass +class Arg_Time: + value: int + + +@dataclass +class Arg_Num: + value: Result[Number, NotInBounds] + + +@dataclass +class Arg_Bool: + value: bool + + +@dataclass +class Arg_Simple: + value: str + + +@dataclass +class Arg_SoundCategory: + value: SoundCategory + + +@dataclass +class Arg_DamageType: + value: str + + +@dataclass +class Arg_Effect: + value: str + + +@dataclass +class Arg_Enchantment: + value: str + + +@dataclass +class Arg_EntityAnchor: + value: EntityAnchor + + +Arg = Union[Arg_Entities, Arg_Entity, Arg_Players, Arg_BlockPos, Arg_Pos3d, Arg_Pos2d, Arg_Rotation, Arg_GameMode, Arg_Difficulty, Arg_Item, Arg_ItemPredicate, Arg_ResourceLocation, Arg_Block, Arg_BlockPredicate, Arg_BossbarColor, Arg_BossbarStyle, Arg_Particle, Arg_Msg, Arg_TextComponent, Arg_Time, Arg_Num, Arg_Bool, Arg_Simple, Arg_SoundCategory, Arg_DamageType, Arg_Effect, Arg_Enchantment, Arg_EntityAnchor] + + +class ConsumedArgs: + + def get_value(self, key: str) -> Arg: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + +@dataclass +class CommandSenderType_Rcon: + pass + + +@dataclass +class CommandSenderType_Console: + pass + + +@dataclass +class CommandSenderType_Player: + value: player.Player + + +@dataclass +class CommandSenderType_CommandBlock: + value: Tuple[entity.CommandBlockEntity, world.World] + + +@dataclass +class CommandSenderType_Dummy: + pass + + +CommandSenderType = Union[CommandSenderType_Rcon, CommandSenderType_Console, CommandSenderType_Player, CommandSenderType_CommandBlock, CommandSenderType_Dummy] + + +class PermissionLevel(Enum): + ZERO = 0 + ONE = 1 + TWO = 2 + THREE = 3 + FOUR = 4 + +class CommandSender: + + def get_command_sender_type(self) -> CommandSenderType: + raise NotImplementedError + def send_message(self, text: text.TextComponent) -> None: + raise NotImplementedError + def set_success_count(self, count: int) -> None: + raise NotImplementedError + def is_player(self) -> bool: + raise NotImplementedError + def is_console(self) -> bool: + raise NotImplementedError + def as_player(self) -> Optional[player.Player]: + raise NotImplementedError + def permission_level(self) -> PermissionLevel: + raise NotImplementedError + def has_permission_level(self, level: PermissionLevel) -> bool: + raise NotImplementedError + def has_permission(self, server: server.Server, node: str) -> bool: + raise NotImplementedError + def position(self) -> Optional[Tuple[float, float, float]]: + raise NotImplementedError + def world(self) -> Optional[world.World]: + raise NotImplementedError + def get_locale(self) -> common.Locale: + raise NotImplementedError + def should_receive_feedback(self) -> bool: + raise NotImplementedError + def should_broadcast_console_to_ops(self) -> bool: + raise NotImplementedError + def should_track_output(self) -> bool: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class StringType(Enum): + SINGLE_WORD = 0 + QUOTABLE = 1 + GREEDY = 2 + + +@dataclass +class ArgumentType_Bool: + pass + + +@dataclass +class ArgumentType_Float: + value: Tuple[Optional[float], Optional[float]] + + +@dataclass +class ArgumentType_Double: + value: Tuple[Optional[float], Optional[float]] + + +@dataclass +class ArgumentType_Integer: + value: Tuple[Optional[int], Optional[int]] + + +@dataclass +class ArgumentType_Long: + value: Tuple[Optional[int], Optional[int]] + + +@dataclass +class ArgumentType_String: + value: StringType + + +@dataclass +class ArgumentType_Entities: + pass + + +@dataclass +class ArgumentType_Entity: + pass + + +@dataclass +class ArgumentType_Players: + pass + + +@dataclass +class ArgumentType_GameProfile: + pass + + +@dataclass +class ArgumentType_BlockPos: + pass + + +@dataclass +class ArgumentType_Position3d: + pass + + +@dataclass +class ArgumentType_Position2d: + pass + + +@dataclass +class ArgumentType_BlockState: + pass + + +@dataclass +class ArgumentType_BlockPredicate: + pass + + +@dataclass +class ArgumentType_Item: + pass + + +@dataclass +class ArgumentType_ItemPredicate: + pass + + +@dataclass +class ArgumentType_Component: + pass + + +@dataclass +class ArgumentType_Rotation: + pass + + +@dataclass +class ArgumentType_ResourceLocation: + pass + + +@dataclass +class ArgumentType_EntityAnchor: + pass + + +@dataclass +class ArgumentType_Gamemode: + pass + + +@dataclass +class ArgumentType_Difficulty: + pass + + +@dataclass +class ArgumentType_Time: + value: int + + +@dataclass +class ArgumentType_Resource: + value: str + + +ArgumentType = Union[ArgumentType_Bool, ArgumentType_Float, ArgumentType_Double, ArgumentType_Integer, ArgumentType_Long, ArgumentType_String, ArgumentType_Entities, ArgumentType_Entity, ArgumentType_Players, ArgumentType_GameProfile, ArgumentType_BlockPos, ArgumentType_Position3d, ArgumentType_Position2d, ArgumentType_BlockState, ArgumentType_BlockPredicate, ArgumentType_Item, ArgumentType_ItemPredicate, ArgumentType_Component, ArgumentType_Rotation, ArgumentType_ResourceLocation, ArgumentType_EntityAnchor, ArgumentType_Gamemode, ArgumentType_Difficulty, ArgumentType_Time, ArgumentType_Resource] + + + +@dataclass +class CommandError_InvalidConsumption: + value: Optional[str] + + +@dataclass +class CommandError_InvalidRequirement: + pass + + +@dataclass +class CommandError_PermissionDenied: + pass + + +@dataclass +class CommandError_CommandFailed: + value: text.TextComponent + + +CommandError = Union[CommandError_InvalidConsumption, CommandError_InvalidRequirement, CommandError_PermissionDenied, CommandError_CommandFailed] + + +class CommandNode: + + @classmethod + def literal(cls, name: str) -> Self: + raise NotImplementedError + @classmethod + def argument(cls, name: str, arg_type: ArgumentType) -> Self: + raise NotImplementedError + def then(self, node: Self) -> None: + raise NotImplementedError + def execute_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def require_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class Command: + + def __init__(self, names: List[str], description: str) -> None: + """ + First name is primary, rest are aliases + """ + raise NotImplementedError + + def then(self, node: CommandNode) -> None: + raise NotImplementedError + def execute_with_handler_id(self, handler_id: int) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/common.py b/src/pumpkin_api/wit_files/wit_world/imports/common.py new file mode 100644 index 0000000..34f064e --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/common.py @@ -0,0 +1,213 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Provides common types and utilities used across the entire plugin API. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class Hand(Enum): + """ + Represents the hands of a player. + """ + LEFT = 0 + RIGHT = 1 + +class GameMode(Enum): + """ + Represents the game modes available in Minecraft. + """ + SURVIVAL = 0 + CREATIVE = 1 + ADVENTURE = 2 + SPECTATOR = 3 + +class NamedColor(Enum): + """ + The 16 base Minecraft colors. + """ + BLACK = 0 + DARK_BLUE = 1 + DARK_GREEN = 2 + DARK_AQUA = 3 + DARK_RED = 4 + DARK_PURPLE = 5 + GOLD = 6 + GRAY = 7 + DARK_GRAY = 8 + BLUE = 9 + GREEN = 10 + AQUA = 11 + RED = 12 + LIGHT_PURPLE = 13 + YELLOW = 14 + WHITE = 15 + +@dataclass +class RgbColor: + """ + An RGB color represented by its red, green, and blue components. + """ + r: int + g: int + b: int + +@dataclass +class ArgbColor: + """ + An ARGB color with alpha (transparency) channel. + """ + a: int + r: int + g: int + b: int + +@dataclass +class ItemStack: + """ + Represents a stack of items with its type and count. + """ + registry_key: str + count: int + +class Locale(Enum): + """ + All Minecraft-supported locales. + """ + AF_ZA = 0 + AR_SA = 1 + AST_ES = 2 + AZ_AZ = 3 + BA_RU = 4 + BAR = 5 + BE_BY = 6 + BG_BG = 7 + BR_FR = 8 + BRB = 9 + BS_BA = 10 + CA_ES = 11 + CS_CZ = 12 + CY_GB = 13 + DA_DK = 14 + DE_AT = 15 + DE_CH = 16 + DE_DE = 17 + EL_GR = 18 + EN_AU = 19 + EN_CA = 20 + EN_GB = 21 + EN_NZ = 22 + EN_PT = 23 + EN_UD = 24 + EN_US = 25 + ENP = 26 + ENWS = 27 + EO_UY = 28 + ES_AR = 29 + ES_CL = 30 + ES_EC = 31 + ES_ES = 32 + ES_MX = 33 + ES_UY = 34 + ES_VE = 35 + ESAN = 36 + ET_EE = 37 + EU_ES = 38 + FA_IR = 39 + FI_FI = 40 + FIL_PH = 41 + FO_FO = 42 + FR_CA = 43 + FR_FR = 44 + FRA_DE = 45 + FUR_IT = 46 + FY_NL = 47 + GA_IE = 48 + GD_GB = 49 + GL_ES = 50 + HAW_US = 51 + HE_IL = 52 + HI_IN = 53 + HR_HR = 54 + HU_HU = 55 + HY_AM = 56 + ID_ID = 57 + IG_NG = 58 + IO_EN = 59 + IS_IS = 60 + ISV = 61 + IT_IT = 62 + JA_JP = 63 + JBO_EN = 64 + KA_GE = 65 + KK_KZ = 66 + KN_IN = 67 + KO_KR = 68 + KSH = 69 + KW_GB = 70 + LA_LA = 71 + LB_LU = 72 + LI_LI = 73 + LMO = 74 + LO_LA = 75 + LOL_US = 76 + LT_LT = 77 + LV_LV = 78 + LZH = 79 + MK_MK = 80 + MN_MN = 81 + MS_MY = 82 + MT_MT = 83 + NAH = 84 + NDS_DE = 85 + NL_BE = 86 + NL_NL = 87 + NN_NO = 88 + NO_NO = 89 + OC_FR = 90 + OVD = 91 + PL_PL = 92 + PT_BR = 93 + PT_PT = 94 + QYA_AA = 95 + RO_RO = 96 + RPR = 97 + RU_RU = 98 + RY_UA = 99 + SAH_SAH = 100 + SE_NO = 101 + SK_SK = 102 + SL_SI = 103 + SO_SO = 104 + SQ_AL = 105 + SR_CS = 106 + SR_SP = 107 + SV_SE = 108 + SXU = 109 + SZL = 110 + TA_IN = 111 + TH_TH = 112 + TL_PH = 113 + TLH_AA = 114 + TOK = 115 + TR_TR = 116 + TT_RU = 117 + UK_UA = 118 + VAL_ES = 119 + VEC_IT = 120 + VI_VN = 121 + YI_DE = 122 + YO_NG = 123 + ZH_CN = 124 + ZH_HK = 125 + ZH_TW = 126 + ZLM_ARAB = 127 + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/context.py b/src/pumpkin_api/wit_files/wit_world/imports/context.py new file mode 100644 index 0000000..3007ef5 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/context.py @@ -0,0 +1,70 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +The core context provided to a plugin during initialization and event handling. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import permission +from ..imports import event +from ..imports import server +from ..imports import command + +class Context: + """ + A contextual handle for plugin-related operations. + """ + + def register_event(self, handler_id: int, event_type: event.EventType, event_priority: event.EventPriority, blocking: bool) -> None: + """ + Registers a handler for a specific event. + + * `handler-id`: Unique ID for the event handler. + * `event-type`: The type of event to listen for. + * `event-priority`: When this handler should be called relative to others. + * `blocking`: Whether the event should wait for this handler to finish. + """ + raise NotImplementedError + def register_command(self, command: command.Command, permission: str) -> None: + """ + Registers a new command. + + * `command`: The command definition. + * `permission`: The required permission node to execute this command. + """ + raise NotImplementedError + def register_permission(self, permission: permission.Permission) -> None: + """ + Registers a permission node with the server. + + Raises: `componentize_py_types.Err(wit_world.imports.str)` + """ + raise NotImplementedError + def get_data_folder(self) -> str: + """ + Returns the path to the plugin's private data folder. + """ + raise NotImplementedError + def get_server(self) -> server.Server: + """ + Returns the global server instance. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/entity.py b/src/pumpkin_api/wit_files/wit_world/imports/entity.py new file mode 100644 index 0000000..6b61c37 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/entity.py @@ -0,0 +1,84 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class BlockEntity: + + def resource_location(self) -> str: + raise NotImplementedError + def get_position(self) -> Tuple[int, int, int]: + raise NotImplementedError + def get_id(self) -> int: + raise NotImplementedError + def is_dirty(self) -> bool: + """ + write-nbt: func(nbt: nbt-compound); + from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; + tick: func(world: simple-world); + write-internal: func(nbt: nbt-compound); + chunk-data-nbt: func() -> option option; + set-block-state: func(block-state: block-state-id); + on-block-replaced: func(world: simple-world, position: block-position); + """ + raise NotImplementedError + def clear_dirty(self) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class CommandBlockEntity: + + def get_block_entity(self) -> BlockEntity: + raise NotImplementedError + def last_output(self) -> str: + raise NotImplementedError + def track_output(self) -> bool: + raise NotImplementedError + def success_count(self) -> int: + raise NotImplementedError + def command(self) -> str: + raise NotImplementedError + def auto(self) -> bool: + raise NotImplementedError + def condition_met(self) -> bool: + raise NotImplementedError + def powered(self) -> bool: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + +@dataclass +class Entity_CommandBlockEntity: + value: CommandBlockEntity + + +Entity = Union[Entity_CommandBlockEntity] + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/event.py b/src/pumpkin_api/wit_files/wit_world/imports/event.py new file mode 100644 index 0000000..62ced7e --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/event.py @@ -0,0 +1,434 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text +from ..imports import world +from ..imports import player + +class EventPriority(Enum): + HIGHEST = 0 + HIGH = 1 + NORMAL = 2 + LOW = 3 + LOWEST = 4 + +@dataclass +class PlayerJoinEventData: + player: player.Player + join_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerLeaveEventData: + player: player.Player + leave_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerLoginEventData: + player: player.Player + kick_message: text.TextComponent + cancelled: bool + +@dataclass +class PlayerChatEventData: + player: player.Player + message: str + recipients: List[player.Player] + cancelled: bool + +@dataclass +class PlayerCommandSendEventData: + player: player.Player + command: str + cancelled: bool + +@dataclass +class PlayerPermissionCheckEventData: + player: player.Player + permission: str + permission_result: bool + +@dataclass +class PlayerMoveEventData: + player: player.Player + from_position: Tuple[float, float, float] + to_position: Tuple[float, float, float] + cancelled: bool + +@dataclass +class PlayerTeleportEventData: + player: player.Player + from_position: Tuple[float, float, float] + to_position: Tuple[float, float, float] + cancelled: bool + +@dataclass +class PlayerChangeWorldEventData: + player: player.Player + previous_world: world.World + new_world: world.World + position: Tuple[float, float, float] + yaw: float + pitch: float + cancelled: bool + +@dataclass +class PlayerExpChangeEventData: + player: player.Player + amount: int + +@dataclass +class PlayerItemHeldEventData: + player: player.Player + previous_slot: int + new_slot: int + cancelled: bool + +@dataclass +class PlayerChangedMainHandEventData: + player: player.Player + main_hand: common.Hand + +@dataclass +class PlayerGamemodeChangeEventData: + player: player.Player + previous_gamemode: common.GameMode + new_gamemode: common.GameMode + cancelled: bool + +@dataclass +class PlayerCustomPayloadEventData: + player: player.Player + channel: str + data: bytes + +class PlayerFishState(Enum): + FISHING = 0 + CAUGHT_FISH = 1 + CAUGHT_ENTITY = 2 + IN_GROUND = 3 + FAILED_ATTEMPT = 4 + REEL_IN = 5 + BITE = 6 + +@dataclass +class PlayerFishEventData: + player: player.Player + caught_uuid: Optional[str] + caught_type: str + hook_uuid: str + state: PlayerFishState + hand: common.Hand + exp_to_drop: int + cancelled: bool + +@dataclass +class PlayerEggThrowEventData: + player: player.Player + egg_uuid: str + hatching: bool + num_hatches: int + hatching_type: str + cancelled: bool + +class EntityInteractionAction(Enum): + INTERACT = 0 + ATTACK = 1 + INTERACT_AT = 2 + +class InteractAction(Enum): + LEFT_CLICK_BLOCK = 0 + LEFT_CLICK_AIR = 1 + RIGHT_CLICK_AIR = 2 + RIGHT_CLICK_BLOCK = 3 + +@dataclass +class PlayerInteractEventData: + player: player.Player + action: InteractAction + clicked_pos: Optional[Tuple[int, int, int]] + item: common.ItemStack + block: str + cancelled: bool + +@dataclass +class PlayerToggleSneakEventData: + player: player.Player + is_sneaking: bool + cancelled: bool + +@dataclass +class PlayerToggleSprintEventData: + player: player.Player + is_sprinting: bool + cancelled: bool + +@dataclass +class PlayerInteractUnknownEntityEventData: + player: player.Player + entity_id: int + action: EntityInteractionAction + cancelled: bool + +@dataclass +class BlockRedstoneEventData: + target_world: world.World + state_id: int + block_position: Tuple[int, int, int] + old_current: int + new_current: int + cancelled: bool + +@dataclass +class BlockBreakEventData: + player: Optional[player.Player] + block: str + block_position: Tuple[int, int, int] + exp: int + should_drop: bool + cancelled: bool + +@dataclass +class BlockBurnEventData: + igniting_block: str + block: str + cancelled: bool + +@dataclass +class BlockCanBuildEventData: + block_to_build: str + buildable: bool + player: player.Player + block: str + cancelled: bool + +@dataclass +class BlockGrowEventData: + target_world: world.World + old_block: str + old_state_id: int + new_block: str + new_state_id: int + block_position: Tuple[int, int, int] + cancelled: bool + +@dataclass +class BlockPlaceEventData: + player: player.Player + block_placed: str + block_placed_against: str + block_position: Tuple[int, int, int] + can_build: bool + cancelled: bool + +@dataclass +class ServerCommandEventData: + command: str + cancelled: bool + +@dataclass +class SpawnChangeEventData: + target_world: world.World + previous_position: Tuple[int, int, int] + previous_yaw: float + previous_pitch: float + new_position: Tuple[int, int, int] + new_yaw: float + new_pitch: float + +@dataclass +class ServerBroadcastEventData: + message: text.TextComponent + sender: text.TextComponent + cancelled: bool + +class EventType(Enum): + PLAYER_JOIN_EVENT = 0 + PLAYER_LEAVE_EVENT = 1 + PLAYER_LOGIN_EVENT = 2 + PLAYER_CHAT_EVENT = 3 + PLAYER_COMMAND_SEND_EVENT = 4 + PLAYER_PERMISSION_CHECK_EVENT = 5 + PLAYER_MOVE_EVENT = 6 + PLAYER_TELEPORT_EVENT = 7 + PLAYER_CHANGE_WORLD_EVENT = 8 + PLAYER_EXP_CHANGE_EVENT = 9 + PLAYER_ITEM_HELD_EVENT = 10 + PLAYER_CHANGED_MAIN_HAND_EVENT = 11 + PLAYER_GAMEMODE_CHANGE_EVENT = 12 + PLAYER_CUSTOM_PAYLOAD_EVENT = 13 + PLAYER_FISH_EVENT = 14 + PLAYER_EGG_THROW_EVENT = 15 + PLAYER_INTERACT_UNKNOWN_ENTITY_EVENT = 16 + PLAYER_INTERACT_EVENT = 17 + PLAYER_TOGGLE_SNEAK_EVENT = 18 + PLAYER_TOGGLE_SPRINT_EVENT = 19 + BLOCK_REDSTONE_EVENT = 20 + BLOCK_BREAK_EVENT = 21 + BLOCK_BURN_EVENT = 22 + BLOCK_CAN_BUILD_EVENT = 23 + BLOCK_GROW_EVENT = 24 + BLOCK_PLACE_EVENT = 25 + SERVER_COMMAND_EVENT = 26 + SPAWN_CHANGE_EVENT = 27 + SERVER_BROADCAST_EVENT = 28 + + +@dataclass +class Event_PlayerJoinEvent: + value: PlayerJoinEventData + + +@dataclass +class Event_PlayerLeaveEvent: + value: PlayerLeaveEventData + + +@dataclass +class Event_PlayerLoginEvent: + value: PlayerLoginEventData + + +@dataclass +class Event_PlayerChatEvent: + value: PlayerChatEventData + + +@dataclass +class Event_PlayerCommandSendEvent: + value: PlayerCommandSendEventData + + +@dataclass +class Event_PlayerPermissionCheckEvent: + value: PlayerPermissionCheckEventData + + +@dataclass +class Event_PlayerMoveEvent: + value: PlayerMoveEventData + + +@dataclass +class Event_PlayerTeleportEvent: + value: PlayerTeleportEventData + + +@dataclass +class Event_PlayerChangeWorldEvent: + value: PlayerChangeWorldEventData + + +@dataclass +class Event_PlayerExpChangeEvent: + value: PlayerExpChangeEventData + + +@dataclass +class Event_PlayerItemHeldEvent: + value: PlayerItemHeldEventData + + +@dataclass +class Event_PlayerChangedMainHandEvent: + value: PlayerChangedMainHandEventData + + +@dataclass +class Event_PlayerGamemodeChangeEvent: + value: PlayerGamemodeChangeEventData + + +@dataclass +class Event_PlayerCustomPayloadEvent: + value: PlayerCustomPayloadEventData + + +@dataclass +class Event_PlayerFishEvent: + value: PlayerFishEventData + + +@dataclass +class Event_PlayerEggThrowEvent: + value: PlayerEggThrowEventData + + +@dataclass +class Event_PlayerInteractUnknownEntityEvent: + value: PlayerInteractUnknownEntityEventData + + +@dataclass +class Event_PlayerInteractEvent: + value: PlayerInteractEventData + + +@dataclass +class Event_PlayerToggleSneakEvent: + value: PlayerToggleSneakEventData + + +@dataclass +class Event_PlayerToggleSprintEvent: + value: PlayerToggleSprintEventData + + +@dataclass +class Event_BlockRedstoneEvent: + value: BlockRedstoneEventData + + +@dataclass +class Event_BlockBreakEvent: + value: BlockBreakEventData + + +@dataclass +class Event_BlockBurnEvent: + value: BlockBurnEventData + + +@dataclass +class Event_BlockCanBuildEvent: + value: BlockCanBuildEventData + + +@dataclass +class Event_BlockGrowEvent: + value: BlockGrowEventData + + +@dataclass +class Event_BlockPlaceEvent: + value: BlockPlaceEventData + + +@dataclass +class Event_ServerCommandEvent: + value: ServerCommandEventData + + +@dataclass +class Event_SpawnChangeEvent: + value: SpawnChangeEventData + + +@dataclass +class Event_ServerBroadcastEvent: + value: ServerBroadcastEventData + + +Event = Union[Event_PlayerJoinEvent, Event_PlayerLeaveEvent, Event_PlayerLoginEvent, Event_PlayerChatEvent, Event_PlayerCommandSendEvent, Event_PlayerPermissionCheckEvent, Event_PlayerMoveEvent, Event_PlayerTeleportEvent, Event_PlayerChangeWorldEvent, Event_PlayerExpChangeEvent, Event_PlayerItemHeldEvent, Event_PlayerChangedMainHandEvent, Event_PlayerGamemodeChangeEvent, Event_PlayerCustomPayloadEvent, Event_PlayerFishEvent, Event_PlayerEggThrowEvent, Event_PlayerInteractUnknownEntityEvent, Event_PlayerInteractEvent, Event_PlayerToggleSneakEvent, Event_PlayerToggleSprintEvent, Event_BlockRedstoneEvent, Event_BlockBreakEvent, Event_BlockBurnEvent, Event_BlockCanBuildEvent, Event_BlockGrowEvent, Event_BlockPlaceEvent, Event_ServerCommandEvent, Event_SpawnChangeEvent, Event_ServerBroadcastEvent] + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/gui.py b/src/pumpkin_api/wit_files/wit_world/imports/gui.py new file mode 100644 index 0000000..f19d5e7 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/gui.py @@ -0,0 +1,61 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text + +class GuiType(Enum): + GENERIC_9X1 = 0 + GENERIC_9X2 = 1 + GENERIC_9X3 = 2 + GENERIC_9X4 = 3 + GENERIC_9X5 = 4 + GENERIC_9X6 = 5 + GENERIC_3X3 = 6 + CRAFTER_3X3 = 7 + ANVIL = 8 + BEACON = 9 + BLAST_FURNACE = 10 + BREWING_STAND = 11 + CRAFTING = 12 + ENCHANTMENT = 13 + FURNACE = 14 + GRINDSTONE = 15 + HOPPER = 16 + LECTERN = 17 + LOOM = 18 + MERCHANT = 19 + SHULKER_BOX = 20 + SMITHING = 21 + SMOKER = 22 + CARTOGRAPHY_TABLE = 23 + STONECUTTER = 24 + +class Gui: + + def __init__(self, type: GuiType, title: text.TextComponent) -> None: + raise NotImplementedError + + def set_item(self, slot: int, item: common.ItemStack) -> None: + raise NotImplementedError + def get_item(self, slot: int) -> Optional[common.ItemStack]: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/i18n.py b/src/pumpkin_api/wit_files/wit_world/imports/i18n.py new file mode 100644 index 0000000..4e50ace --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/i18n.py @@ -0,0 +1,35 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common + + +def translate(key: str, locale: common.Locale) -> str: + """ + Retrieves a translated string from the host. + + # Arguments + * `key`: The namespaced translation key (e.g. `pumpkin:my_key`). + * `locale`: The target locale. + + # Returns + The localized string, falling back to English or the key itself. + """ + raise NotImplementedError +def load_translations(namespace: str, json: str, locale: common.Locale) -> None: + """ + Loads custom translations from a JSON string. + + # Arguments + * `namespace`: The namespace to associate these keys with. + * `json`: A JSON string containing a flat map of translations. + * `locale`: The target locale for these translations. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit_files/wit_world/imports/logging.py b/src/pumpkin_api/wit_files/wit_world/imports/logging.py new file mode 100644 index 0000000..a2e59cf --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/logging.py @@ -0,0 +1,30 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class Level(Enum): + TRACE = 0 + DEBUG = 1 + INFO = 2 + WARN = 3 + ERROR = 4 + + +def log(level: Level, message: str) -> None: + """ + log any general purpose message + """ + raise NotImplementedError +def log_tracing(event: bytes) -> None: + """ + This function is meant to be used by the tracing crate. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit_files/wit_world/imports/permission.py b/src/pumpkin_api/wit_files/wit_world/imports/permission.py new file mode 100644 index 0000000..4718bfe --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/permission.py @@ -0,0 +1,71 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + +class PermissionLevel(Enum): + """ + Represents the player's permission level + + Permission levels determine the player's access to commands and server operations. + Each numeric level corresponds to a specific role: + - `Zero`: `normal`: Player can use basic commands. + - `One`: `moderator`: Player can bypass spawn protection. + - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks. + - `Three`: `admin`: Player or executor can use commands related to multiplayer management. + - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. + """ + ZERO = 0 + ONE = 1 + TWO = 2 + THREE = 3 + FOUR = 4 + + +@dataclass +class PermissionDefault_Deny: + pass + + +@dataclass +class PermissionDefault_Allow: + pass + + +@dataclass +class PermissionDefault_Op: + value: PermissionLevel + + +PermissionDefault = Union[PermissionDefault_Deny, PermissionDefault_Allow, PermissionDefault_Op] +""" +Describes the default behavior for permissions +""" + + +@dataclass +class PermissionChild: + """ + A child permission entry + """ + node: str + value: bool + +@dataclass +class Permission: + """ + Defines a permission node in the system + """ + node: str + description: str + default: PermissionDefault + children: List[PermissionChild] + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/player.py b/src/pumpkin_api/wit_files/wit_world/imports/player.py new file mode 100644 index 0000000..b8c2bd9 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/player.py @@ -0,0 +1,350 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Represents a player connected to the Minecraft server. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import permission +from ..imports import text +from ..imports import gui +from ..imports import world + +@dataclass +class PlayerAbilities: + """ + Various abilities a player can have. + """ + invulnerable: bool + flying: bool + allow_flying: bool + creative: bool + allow_modify_world: bool + fly_speed: float + walk_speed: float + +class Player: + """ + A handle to a player on the server. + """ + + def get_id(self) -> str: + """ + Returns the player's unique UUID as a string. + """ + raise NotImplementedError + def get_name(self) -> str: + """ + Returns the player's username. + """ + raise NotImplementedError + def get_position(self) -> Tuple[float, float, float]: + """ + Basic state + Returns the player's current position in the world. + """ + raise NotImplementedError + def get_yaw(self) -> float: + """ + Returns the player's current yaw (horizontal rotation). + """ + raise NotImplementedError + def get_pitch(self) -> float: + """ + Returns the player's current pitch (vertical rotation). + """ + raise NotImplementedError + def get_world(self) -> world.World: + """ + Returns the world the player is currently in. + """ + raise NotImplementedError + def get_gamemode(self) -> common.GameMode: + """ + Returns the player's current game mode. + """ + raise NotImplementedError + def set_gamemode(self, mode: common.GameMode) -> bool: + """ + Changes the player's game mode. Returns true if successful. + """ + raise NotImplementedError + def get_locale(self) -> str: + """ + Returns the player's locale (e.g., "en_us"). + """ + raise NotImplementedError + def get_ping(self) -> int: + """ + Returns the player's current ping in milliseconds. + """ + raise NotImplementedError + def get_permission_level(self) -> permission.PermissionLevel: + """ + Permissions & metadata + Returns the player's permission level (0-4). + """ + raise NotImplementedError + def set_permission_level(self, level: permission.PermissionLevel) -> None: + """ + Sets the player's permission level. + """ + raise NotImplementedError + def has_permission(self, node: str) -> bool: + """ + Checks if the player has a specific permission node. + """ + raise NotImplementedError + def get_display_name(self) -> text.TextComponent: + """ + Returns the player's display name, which may include formatting. + """ + raise NotImplementedError + def send_system_message(self, text: text.TextComponent, overlay: bool) -> None: + """ + Messaging & titles + Sends a system message to the player's chat. + """ + raise NotImplementedError + def show_title(self, text: text.TextComponent) -> None: + """ + Shows a large title on the player's screen. + """ + raise NotImplementedError + def show_subtitle(self, text: text.TextComponent) -> None: + """ + Shows a smaller subtitle on the player's screen. + """ + raise NotImplementedError + def show_actionbar(self, text: text.TextComponent) -> None: + """ + Shows a message in the action bar area. + """ + raise NotImplementedError + def send_title_animation(self, fade_in: int, stay: int, fade_out: int) -> None: + """ + Sets the fade-in, stay, and fade-out times for titles. + """ + raise NotImplementedError + def teleport(self, position: Tuple[float, float, float], yaw: Optional[float], pitch: Optional[float], world: world.World) -> None: + """ + Teleport & kick + Teleports the player to a position in a world. + """ + raise NotImplementedError + def teleport_world(self, world_ref: world.World, position: Tuple[float, float, float], yaw: Optional[float], pitch: Optional[float]) -> None: + """ + Teleports the player to another world at a specific position. + """ + raise NotImplementedError + def kick(self, message: text.TextComponent) -> None: + """ + Kicks the player from the server with the specified reason. + """ + raise NotImplementedError + def respawn(self) -> None: + """ + Forces the player to respawn. + """ + raise NotImplementedError + def open_gui(self, gui_ref: gui.Gui) -> None: + """ + Opens a GUI for the player. + """ + raise NotImplementedError + def ban(self, reason: Optional[text.TextComponent]) -> None: + """ + Bans the player with an optional reason. + """ + raise NotImplementedError + def ban_ip(self, reason: Optional[text.TextComponent]) -> None: + """ + Bans the player's IP address with an optional reason. + """ + raise NotImplementedError + def get_selected_slot(self) -> int: + """ + Inventory + Returns the index of the currently selected hotbar slot (0-8). + """ + raise NotImplementedError + def get_item_in_hand(self, hand: common.Hand) -> Optional[common.ItemStack]: + """ + Returns the item currently in the specified hand. + """ + raise NotImplementedError + def get_inventory_item(self, slot: int) -> Optional[common.ItemStack]: + """ + Returns the item in the specified inventory slot. + """ + raise NotImplementedError + def get_health(self) -> float: + """ + Status + Returns the player's current health. + """ + raise NotImplementedError + def set_health(self, health: float) -> None: + """ + Sets the player's current health. + """ + raise NotImplementedError + def get_max_health(self) -> float: + """ + Returns the player's maximum health. + """ + raise NotImplementedError + def set_max_health(self, max_health: float) -> None: + """ + Sets the player's maximum health. + """ + raise NotImplementedError + def get_food_level(self) -> int: + """ + Returns the player's food level (0-20). + """ + raise NotImplementedError + def set_food_level(self, food_level: int) -> None: + """ + Sets the player's food level. + """ + raise NotImplementedError + def get_saturation(self) -> float: + """ + Returns the player's food saturation level. + """ + raise NotImplementedError + def set_saturation(self, saturation: float) -> None: + """ + Sets the player's food saturation level. + """ + raise NotImplementedError + def get_exhaustion(self) -> float: + """ + Returns the player's food exhaustion level. + """ + raise NotImplementedError + def set_exhaustion(self, exhaustion: float) -> None: + """ + Sets the player's food exhaustion level. + """ + raise NotImplementedError + def get_absorption(self) -> float: + """ + Returns the player's current absorption amount. + """ + raise NotImplementedError + def set_absorption(self, absorption: float) -> None: + """ + Sets the player's current absorption amount. + """ + raise NotImplementedError + def get_experience_level(self) -> int: + """ + Returns the player's total experience level. + """ + raise NotImplementedError + def get_experience_progress(self) -> float: + """ + Returns the player's progress toward the next level (0.0 to 1.0). + """ + raise NotImplementedError + def get_experience_points(self) -> int: + """ + Returns the total experience points the player has. + """ + raise NotImplementedError + def set_experience_level(self, level: int) -> None: + """ + Sets the player's total experience level. + """ + raise NotImplementedError + def set_experience_progress(self, progress: float) -> None: + """ + Sets the player's progress toward the next level. + """ + raise NotImplementedError + def set_experience_points(self, points: int) -> None: + """ + Sets the player's total experience points. + """ + raise NotImplementedError + def add_experience_levels(self, levels: int) -> None: + """ + Adds experience levels to the player. + """ + raise NotImplementedError + def add_experience_points(self, points: int) -> None: + """ + Adds experience points to the player. + """ + raise NotImplementedError + def is_sneaking(self) -> bool: + """ + Returns whether the player is currently sneaking. + """ + raise NotImplementedError + def set_sneaking(self, sneaking: bool) -> None: + """ + Sets whether the player is currently sneaking. + """ + raise NotImplementedError + def is_sprinting(self) -> bool: + """ + Returns whether the player is currently sprinting. + """ + raise NotImplementedError + def set_sprinting(self, sprinting: bool) -> None: + """ + Sets whether the player is currently sprinting. + """ + raise NotImplementedError + def is_on_ground(self) -> bool: + """ + Returns whether the player is currently on the ground. + """ + raise NotImplementedError + def is_flying(self) -> bool: + """ + Returns whether the player is currently flying. + """ + raise NotImplementedError + def set_flying(self, flying: bool) -> None: + """ + Sets whether the player should be flying. + """ + raise NotImplementedError + def get_abilities(self) -> PlayerAbilities: + """ + Returns the player's current abilities. + """ + raise NotImplementedError + def set_abilities(self, abilities: PlayerAbilities) -> None: + """ + Updates the player's abilities. + """ + raise NotImplementedError + def get_ip(self) -> str: + """ + Returns the player's IP address. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/scheduler.py b/src/pumpkin_api/wit_files/wit_world/imports/scheduler.py new file mode 100644 index 0000000..8b8a5b1 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/scheduler.py @@ -0,0 +1,44 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Provides functionality to schedule tasks to be executed later on the server. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some + + + +def schedule_delayed_task(handler_id: int, delay_ticks: int) -> int: + """ + Schedules a task to be executed once after the specified number of ticks. + + * `handler-id`: The unique ID of the handler to call when the task executes. + * `delay-ticks`: The number of game ticks to wait before execution (20 ticks = 1 second). + + Returns a unique task ID that can be used to cancel the task. + """ + raise NotImplementedError +def schedule_repeating_task(handler_id: int, delay_ticks: int, period_ticks: int) -> int: + """ + Schedules a task to be executed repeatedly. + + * `handler-id`: The unique ID of the handler to call when the task executes. + * `delay-ticks`: The number of game ticks to wait before the first execution. + * `period-ticks`: The number of game ticks between subsequent executions. + + Returns a unique task ID that can be used to cancel the task. + """ + raise NotImplementedError +def cancel_task(task_id: int) -> None: + """ + Cancels a scheduled task if it hasn't finished yet. + + * `task-id`: The ID of the task to cancel. + """ + raise NotImplementedError diff --git a/src/pumpkin_api/wit_files/wit_world/imports/scoreboard.py b/src/pumpkin_api/wit_files/wit_world/imports/scoreboard.py new file mode 100644 index 0000000..c75b352 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/scoreboard.py @@ -0,0 +1,95 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common +from ..imports import text + +class RenderType(Enum): + INTEGER = 0 + HEARTS = 1 + +class DisplaySlot(Enum): + PLAYER_LIST = 0 + SIDEBAR = 1 + BELOW_NAME = 2 + SIDEBAR_TEAM_BLACK = 3 + SIDEBAR_TEAM_DARK_BLUE = 4 + SIDEBAR_TEAM_DARK_GREEN = 5 + SIDEBAR_TEAM_DARK_AQUA = 6 + SIDEBAR_TEAM_DARK_RED = 7 + SIDEBAR_TEAM_DARK_PURPLE = 8 + SIDEBAR_TEAM_GOLD = 9 + SIDEBAR_TEAM_GRAY = 10 + SIDEBAR_TEAM_DARK_GRAY = 11 + SIDEBAR_TEAM_BLUE = 12 + SIDEBAR_TEAM_GREEN = 13 + SIDEBAR_TEAM_AQUA = 14 + SIDEBAR_TEAM_RED = 15 + SIDEBAR_TEAM_LIGHT_PURPLE = 16 + SIDEBAR_TEAM_YELLOW = 17 + SIDEBAR_TEAM_WHITE = 18 + +class NametagVisibility(Enum): + ALWAYS = 0 + NEVER = 1 + HIDE_FOR_OTHER_TEAMS = 2 + HIDE_FOR_OWN_TEAM = 3 + +class CollisionRule(Enum): + ALWAYS = 0 + NEVER = 1 + PUSH_OTHER_TEAMS = 2 + PUSH_OWN_TEAM = 3 + +@dataclass +class TeamSettings: + display_name: text.TextComponent + friendly_fire: bool + see_friendly_invisibles: bool + nametag_visibility: NametagVisibility + collision_rule: CollisionRule + color: common.NamedColor + prefix: text.TextComponent + suffix: text.TextComponent + +class Scoreboard: + + def add_objective(self, name: str, display_name: text.TextComponent, render_type: RenderType) -> None: + raise NotImplementedError + def remove_objective(self, name: str) -> None: + raise NotImplementedError + def set_display_slot(self, slot: DisplaySlot, objective_name: str) -> None: + raise NotImplementedError + def update_score(self, entity_name: str, objective_name: str, value: int) -> None: + raise NotImplementedError + def remove_score(self, entity_name: str, objective_name: str) -> None: + raise NotImplementedError + def create_team(self, name: str, settings: TeamSettings) -> None: + raise NotImplementedError + def remove_team(self, name: str) -> None: + raise NotImplementedError + def update_team(self, name: str, settings: TeamSettings) -> None: + raise NotImplementedError + def add_player_to_team(self, team_name: str, player_name: str) -> None: + raise NotImplementedError + def remove_player_from_team(self, team_name: str, player_name: str) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/server.py b/src/pumpkin_api/wit_files/wit_world/imports/server.py new file mode 100644 index 0000000..fcde6e2 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/server.py @@ -0,0 +1,75 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import player + +class Difficulty(Enum): + """ + Represents the difficulty setting of a Minecraft server. + """ + PEACEFUL = 0 + EASY = 1 + NORMAL = 2 + HARD = 3 + +class Server: + """ + Represents the global server instance. + """ + + def get_difficulty(self) -> Difficulty: + """ + Returns the current difficulty level of the server. + """ + raise NotImplementedError + def get_player_count(self) -> int: + """ + Returns the total number of players currently connected to the server. + """ + raise NotImplementedError + def get_mspt(self) -> float: + """ + Returns the rolling average Milliseconds Per Tick (MSPT). + A value below 50.0 is required for a stable 20 TPS. + """ + raise NotImplementedError + def get_tps(self) -> float: + """ + Returns the effective Ticks Per Second (TPS). + Ideal value is 20.0. + """ + raise NotImplementedError + def get_all_players(self) -> List[player.Player]: + """ + Returns a list of all online players across all worlds. + """ + raise NotImplementedError + def get_player_by_name(self, name: str) -> Optional[player.Player]: + """ + Searches for a player by their exact name. + """ + raise NotImplementedError + def get_player_by_uuid(self, id: str) -> Optional[player.Player]: + """ + Searches for a player by their UUID string. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/text.py b/src/pumpkin_api/wit_files/wit_world/imports/text.py new file mode 100644 index 0000000..6520805 --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/text.py @@ -0,0 +1,89 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import common + +class TextComponent: + + @classmethod + def text(cls, plain: str) -> Self: + raise NotImplementedError + @classmethod + def translate(cls, key: str, with_: List[Self]) -> Self: + raise NotImplementedError + def add_child(self, child: Self) -> None: + raise NotImplementedError + def add_text(self, text: str) -> None: + raise NotImplementedError + def get_text(self) -> str: + raise NotImplementedError + def encode(self) -> bytes: + raise NotImplementedError + def color_named(self, color: common.NamedColor) -> None: + """ + Style + """ + raise NotImplementedError + def color_rgb(self, color: common.RgbColor) -> None: + raise NotImplementedError + def bold(self, value: bool) -> None: + raise NotImplementedError + def italic(self, value: bool) -> None: + raise NotImplementedError + def underlined(self, value: bool) -> None: + raise NotImplementedError + def strikethrough(self, value: bool) -> None: + raise NotImplementedError + def obfuscated(self, value: bool) -> None: + raise NotImplementedError + def insertion(self, text: str) -> None: + """ + Text inserted into chat when shift-clicked + """ + raise NotImplementedError + def font(self, font: str) -> None: + raise NotImplementedError + def shadow_color(self, color: common.ArgbColor) -> None: + raise NotImplementedError + def click_open_url(self, url: str) -> None: + """ + Click events + """ + raise NotImplementedError + def click_run_command(self, command: str) -> None: + raise NotImplementedError + def click_suggest_command(self, command: str) -> None: + raise NotImplementedError + def click_copy_to_clipboard(self, text: str) -> None: + raise NotImplementedError + def hover_show_text(self, text: Self) -> None: + """ + Hover events + """ + raise NotImplementedError + def hover_show_item(self, item: str) -> None: + """ + Item data as SNBT string + """ + raise NotImplementedError + def hover_show_entity(self, entity_type: str, id: str, name: Optional[Self]) -> None: + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/wit_world/imports/world.py b/src/pumpkin_api/wit_files/wit_world/imports/world.py new file mode 100644 index 0000000..fa5a13a --- /dev/null +++ b/src/pumpkin_api/wit_files/wit_world/imports/world.py @@ -0,0 +1,215 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import text +from ..imports import scoreboard + +class PistonBehavior(Enum): + """ + Defines how a block reacts when pushed by a piston. + """ + NORMAL = 0 + DESTROY = 1 + BLOCK = 2 + IGNORE = 3 + PUSH_ONLY = 4 + +class SoundCategory(Enum): + """ + Categories for sounds to control their volume independently. + """ + MASTER = 0 + MUSIC = 1 + RECORDS = 2 + WEATHER = 3 + BLOCKS = 4 + HOSTILE = 5 + NEUTRAL = 6 + PLAYERS = 7 + AMBIENT = 8 + VOICE = 9 + +class ExplosionInteraction(Enum): + """ + Defines how an explosion interacts with blocks in the world. + """ + NONE = 0 + BLOCK = 1 + MOB = 2 + TNT = 3 + TRIGGER = 4 + +class BlockFlags(Flag): + """ + Flags used to control block updates and notifications. + """ + NOTIFY_NEIGHBORS = auto() + NOTIFY_LISTENERS = auto() + FORCE_STATE = auto() + SKIP_DROPS = auto() + MOVED = auto() + SKIP_REDSTONE_WIRE_STATE_REPLACEMENT = auto() + SKIP_BLOCK_ENTITY_REPLACED_CALLBACK = auto() + SKIP_BLOCK_ADDED_CALLBACK = auto() + +@dataclass +class BlockState: + """ + Detailed information about a block's state. + """ + id: int + luminance: int + opacity: int + hardness: float + is_air: bool + is_liquid: bool + is_solid: bool + is_full_cube: bool + piston_behavior: PistonBehavior + has_random_ticks: bool + +@dataclass +class BlockStateId: + """ + A simple wrapper for a block state ID. + """ + id: int + +@dataclass +class BlockPos: + """ + A position in the world grid. + """ + x: int + y: int + z: int + +class World: + """ + Represents a Minecraft world (dimension). + """ + + def get_id(self) -> str: + """ + Returns the unique identifier of this world. + """ + raise NotImplementedError + def get_block_state_id(self, pos: BlockPos) -> int: + """ + Gets the block state ID at the specified position. + """ + raise NotImplementedError + def get_block_state(self, pos: BlockPos) -> BlockState: + """ + Gets detailed block state information at the specified position. + """ + raise NotImplementedError + def set_block_state(self, pos: BlockPos, state: int, update_flags: BlockFlags) -> None: + """ + Sets the block state at the specified position. + """ + raise NotImplementedError + def get_time_of_day(self) -> int: + """ + Returns the current time of day in ticks. + """ + raise NotImplementedError + def set_time_of_day(self, time: int) -> None: + """ + Sets the current time of day in ticks. + """ + raise NotImplementedError + def get_world_age(self) -> int: + """ + Returns the total age of the world in ticks. + """ + raise NotImplementedError + def get_dimension(self) -> str: + """ + Returns the dimension type (e.g., "minecraft:overworld"). + """ + raise NotImplementedError + def get_top_block_y(self, x: int, z: int) -> int: + """ + Gets the highest non-air block Y coordinate at the specified X and Z. + """ + raise NotImplementedError + def get_motion_blocking_height(self, x: int, z: int) -> int: + """ + Gets the highest motion-blocking block Y coordinate. + """ + raise NotImplementedError + def is_raining(self) -> bool: + """ + Returns whether it is currently raining or snowing. + """ + raise NotImplementedError + def set_raining(self, raining: bool) -> None: + """ + Sets whether it should be raining or snowing. + """ + raise NotImplementedError + def is_thundering(self) -> bool: + """ + Returns whether there is currently a thunderstorm. + """ + raise NotImplementedError + def set_thundering(self, thundering: bool) -> None: + """ + Sets whether there should be a thunderstorm. + """ + raise NotImplementedError + def broadcast_system_message(self, message: text.TextComponent, overlay: bool) -> None: + """ + Broadcasts a system message to all players in this world. + """ + raise NotImplementedError + def get_scoreboard(self) -> scoreboard.Scoreboard: + """ + Returns the scoreboard associated with this world. + """ + raise NotImplementedError + def play_sound(self, sound: str, category: SoundCategory, pos: Tuple[float, float, float], volume: float, pitch: float) -> None: + """ + Plays a sound at the specified position for all players in the world. + """ + raise NotImplementedError + def spawn_particle(self, particle: str, pos: Tuple[float, float, float], offset: Tuple[float, float, float], max_speed: float, count: int) -> None: + """ + Spawns particles at the specified position. + """ + raise NotImplementedError + def create_explosion(self, pos: Tuple[float, float, float], power: float, create_fire: bool, interaction: ExplosionInteraction) -> None: + """ + Creates an explosion at the specified position. + """ + raise NotImplementedError + def get_sea_level(self) -> int: + """ + Returns the sea level of this world. + """ + raise NotImplementedError + def get_min_y(self) -> int: + """ + Returns the minimum Y coordinate (bottom of the world). + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/pumpkin_api/wit_files/world.wit b/src/pumpkin_api/wit_files/world.wit new file mode 100644 index 0000000..c1ac9c7 --- /dev/null +++ b/src/pumpkin_api/wit_files/world.wit @@ -0,0 +1,169 @@ +interface %world { + use common.{position}; + use text.{text-component}; + use scoreboard.{scoreboard}; + + /// Defines how a block reacts when pushed by a piston. + enum piston-behavior { + /// Normal behavior; the block can be pushed and pulled. + normal, + /// The block is destroyed when pushed. + destroy, + /// The block cannot be pushed or pulled. + block, + /// The block is ignored by pistons. + ignore, + /// The block can only be pushed, not pulled. + push-only + } + + /// Categories for sounds to control their volume independently. + enum sound-category { + master, + music, + records, + weather, + blocks, + hostile, + neutral, + players, + ambient, + voice, + } + + /// Defines how an explosion interacts with blocks in the world. + enum explosion-interaction { + /// No interaction with blocks. + none, + /// Blocks are destroyed and may drop loot. + block, + /// Interaction typical for mob-caused explosions. + mob, + /// Interaction typical for TNT explosions. + tnt, + /// Triggers specific block behaviors. + trigger, + } + + /// Flags used to control block updates and notifications. + flags block-flags { + /// Notifies neighbor blocks of the change. + notify-neighbors, + /// Notifies client listeners of the change. + notify-listeners, + /// Forces the block state even if it's the same. + force-state, + /// Skips dropping items when the block is removed. + skip-drops, + /// Indicates the block was moved (e.g., by a piston). + moved, + /// Skips state replacement for redstone wires. + skip-redstone-wire-state-replacement, + /// Skips the callback when a block entity is replaced. + skip-block-entity-replaced-callback, + /// Skips the callback when a block is added. + skip-block-added-callback, + } + + /// Detailed information about a block's state. + record block-state { + /// The internal numerical ID of the block state. + id: u16, + /// The light level emitted by this block (0-15). + luminance: u8, + /// How much light this block blocks. + opacity: u8, + /// The hardness of the block, determining mining speed. + hardness: f32, + /// Whether this block is considered air. + is-air: bool, + /// Whether this block is a liquid. + is-liquid: bool, + /// Whether this block is a solid. + is-solid: bool, + /// Whether this block occupies a full cube. + is-full-cube: bool, + /// How this block behaves when pushed by a piston. + piston-behavior: piston-behavior, + /// Whether this block receives random ticks. + has-random-ticks: bool, + } + + /// A simple wrapper for a block state ID. + record block-state-id { + id: u16, + } + + /// A position in the world grid. + record block-pos { + x: s32, + y: s32, + z: s32, + } + + /// Represents a Minecraft world (dimension). + resource %world { + /// Returns the unique identifier of this world. + get-id: func() -> string; + + /// Gets the block state ID at the specified position. + get-block-state-id: func(pos: block-pos) -> u16; + + /// Gets detailed block state information at the specified position. + get-block-state: func(pos: block-pos) -> block-state; + + /// Sets the block state at the specified position. + set-block-state: func(pos: block-pos, state: u16, update-flags: block-flags); + + /// Returns the current time of day in ticks. + get-time-of-day: func() -> u64; + + /// Sets the current time of day in ticks. + set-time-of-day: func(time: u64); + + /// Returns the total age of the world in ticks. + get-world-age: func() -> u64; + + /// Returns the dimension type (e.g., "minecraft:overworld"). + get-dimension: func() -> string; + + /// Gets the highest non-air block Y coordinate at the specified X and Z. + get-top-block-y: func(x: s32, z: s32) -> s32; + + /// Gets the highest motion-blocking block Y coordinate. + get-motion-blocking-height: func(x: s32, z: s32) -> s32; + + /// Returns whether it is currently raining or snowing. + is-raining: func() -> bool; + + /// Sets whether it should be raining or snowing. + set-raining: func(raining: bool); + + /// Returns whether there is currently a thunderstorm. + is-thundering: func() -> bool; + + /// Sets whether there should be a thunderstorm. + set-thundering: func(thundering: bool); + + /// Broadcasts a system message to all players in this world. + broadcast-system-message: func(message: text-component, overlay: bool); + + /// Returns the scoreboard associated with this world. + get-scoreboard: func() -> scoreboard; + + /// Plays a sound at the specified position for all players in the world. + play-sound: func(sound: string, category: sound-category, pos: position, volume: f32, pitch: f32); + + /// Spawns particles at the specified position. + spawn-particle: func(particle: string, pos: position, offset: position, max-speed: f32, count: s32); + + /// Creates an explosion at the specified position. + create-explosion: func(pos: position, power: f32, create-fire: bool, interaction: explosion-interaction); + + /// Returns the sea level of this world. + get-sea-level: func() -> s32; + + /// Returns the minimum Y coordinate (bottom of the world). + get-min-y: func() -> s32; + } +}