├── .poggit.yml
├── LICENSE
├── README.md
├── logo.png
├── plugin.yml
├── resources
└── config.yml
└── src
├── Customies.php
├── CustomiesListener.php
├── block
├── BlockPalette.php
├── CustomiesBlockFactory.php
├── Material.php
├── Model.php
└── permutations
│ ├── BlockProperty.php
│ ├── Permutable.php
│ ├── Permutation.php
│ ├── Permutations.php
│ └── RotatableTrait.php
├── entity
└── CustomiesEntityFactory.php
├── item
├── CreativeInventoryInfo.php
├── CustomiesItemFactory.php
├── ItemComponents.php
├── ItemComponentsTrait.php
├── component
│ ├── AllowOffHandComponent.php
│ ├── ArmorComponent.php
│ ├── ChargeableComponent.php
│ ├── CooldownComponent.php
│ ├── CreativeCategoryComponent.php
│ ├── CreativeGroupComponent.php
│ ├── DiggerComponent.php
│ ├── DisplayNameComponent.php
│ ├── DurabilityComponent.php
│ ├── FoilComponent.php
│ ├── FoodComponent.php
│ ├── FuelComponent.php
│ ├── HandEquippedComponent.php
│ ├── IconComponent.php
│ ├── ItemComponent.php
│ ├── KnockbackResistanceComponent.php
│ ├── MaxStackSizeComponent.php
│ ├── ProjectileComponent.php
│ ├── RenderOffsetsComponent.php
│ ├── ThrowableComponent.php
│ ├── UseAnimationComponent.php
│ ├── UseDurationComponent.php
│ └── WearableComponent.php
└── types
│ ├── AxeItem.php
│ ├── BasicItem.php
│ ├── CustomArmor.php
│ ├── FoodItem.php
│ ├── HoeItem.php
│ ├── PickaxeItem.php
│ ├── ShovelItem.php
│ └── SwordItem.php
├── player
├── CustomPlayer.php
└── SurvivalBlockHandler.php
├── task
└── AsyncRegisterBlocksTask.php
├── util
├── Cache.php
└── NBT.php
└── world
├── LegacyBlockIdToStringIdMap.php
└── LevelDB.php
/.poggit.yml:
--------------------------------------------------------------------------------
1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/Refaltor77/CustomItemAPI
2 | build-by-default: true
3 | branches:
4 | - main
5 | projects:
6 | CustomItemAPI:
7 | path: ""
8 | icon: logo.png
9 | ...
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to CustomItemAPI (Update Customies ! Very good plugin !)
2 |
3 | > the Plugin allows you to add as many items as you want
4 | > on your server, you can add new foods,
5 | > New pickaxes and much more !
6 | > A configuration is present for people
7 | > not having the necessary development skills. !!!
8 |
9 |
10 | ## features
11 | - Add block config system
12 | - Add entitie config system
13 | - Add json config for complexe item (ex: item execute /feed)
14 |
15 | ````YAML
16 | # CustomItemAPI configuration v1.0.0.
17 | # CustomItemAPI is a plugin that allows
18 | # to add items to your server with Customies plugin (very good plugin but is not for the user basic).
19 | # The plugin supports:
20 | # ------------------
21 | # - CustomFood
22 | # - CustomPotion (animation)
23 | # - CustomArmor
24 | # - CustomPickaxe
25 | # - CustomShovel
26 | # - CustomHoe
27 | # - CustomAxe
28 | # - CustomSword
29 | # ------------------
30 | # For all requests for assistance
31 | # Discord: Refaltor#6969
32 | # github: https://github.com/refaltor77
33 |
34 |
35 |
36 | basic_items:
37 | example_basic_item: # This name is used only if the "name" value does not exist.
38 | name: test # never use space in your item names | required.
39 | texture_path: stick # texture present only in the textures/items folder of your pack | required
40 | max_stack_size: 16 # optional | default is 64
41 | allow_off_hand: false # optional | default is false
42 | render_offset: 16 # 32 / 64 / 128 etc...
43 |
44 | food_items:
45 | example_food_item:
46 | name: test_food # Always use "_" for spaces.
47 | is_potion: false # if true, The animation of eating will be like a potion. | optional default is false
48 | texture_path: apple # required
49 | max_stack_size: 32 # optional | default is 64
50 | can_always_eat: true # This allows you to always eat the item even if the player is not hungry | optional default is false
51 | food_restore: 4 # use only integer numbers for this value. | required
52 | saturation_restore: 15.0 # use only float numbers for this value. | required
53 | render_offset: 16 # 32 / 64 / 128 etc...
54 |
55 | tool_items:
56 | example_tool:
57 | name: test_pickaxe
58 | type: pickaxe # pickaxe | axe | shovel | sword | hoe - required
59 | tier: diamond # diamond | golden | iron | stone | wooden - required
60 | texture_path: apple # required
61 | mining_speed: 3.0 # use only float numbers for this value. | required
62 | durability: 100 # required
63 | attack_point: 2 # required
64 | render_offset: 16 # 32 / 64 / 128 etc...
65 |
66 | armor_items:
67 | example_helmet:
68 | name: test_helmet
69 | type: helmet # helmet | chestplate | leggings | boots - required
70 | texture_path: iron_helmet
71 | defense_points: 5 # required
72 | durability: 500 # required
73 | render_offset: 16 # 32 / 64 / 128 etc...
74 | ````
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Refaltor77/CustomItemAPI/4b2abcfb66572ffa8772e0d450bfa27c0436e573/logo.png
--------------------------------------------------------------------------------
/plugin.yml:
--------------------------------------------------------------------------------
1 | name: CustomItemAPI
2 | description: CustomItemAPI
3 |
4 | main: customiesdevs\customies\Customies
5 | src-namespace-prefix: customiesdevs\customies
6 | version: 4.0.0
7 | api: 4.0.0
8 |
9 | authors:
10 | - DenielWorld
11 | - TwistedAsylumMC
12 | - Refaltor # only for the CustomItemAPI
13 | contributors:
14 | - JackNoordhuis
15 | - Unickorn
16 | - abimek
17 |
--------------------------------------------------------------------------------
/resources/config.yml:
--------------------------------------------------------------------------------
1 | # CustomItemAPI configuration v1.0.0.
2 | # CustomItemAPI is a plugin that allows
3 | # to add items to your server with Customies plugin (very good plugin but is not for the user basic).
4 | # The plugin supports:
5 | # ------------------
6 | # - CustomFood
7 | # - CustomPotion (animation)
8 | # - CustomArmor
9 | # - CustomPickaxe
10 | # - CustomShovel
11 | # - CustomHoe
12 | # - CustomAxe
13 | # - CustomSword
14 | # ------------------
15 | # For all requests for assistance
16 | # Discord: Refaltor#6969
17 | # github: https://github.com/refaltor77
18 |
19 |
20 |
21 | basic_items:
22 | example_basic_item: # This name is used only if the "name" value does not exist.
23 | name: test # never use space in your item names | required.
24 | texture_path: stick # texture present only in the textures/items folder of your pack | required
25 | max_stack_size: 16 # optional | default is 64
26 | allow_off_hand: false # optional | default is false
27 | render_offset: 16 # 32 / 64 / 128 etc...
28 |
29 | food_items:
30 | example_food_item:
31 | name: test_food # Always use "_" for spaces.
32 | is_potion: false # if true, The animation of eating will be like a potion. | optional default is false
33 | texture_path: apple # required
34 | max_stack_size: 32 # optional | default is 64
35 | can_always_eat: true # This allows you to always eat the item even if the player is not hungry | optional default is false
36 | food_restore: 4 # use only integer numbers for this value. | required
37 | saturation_restore: 15.0 # use only float numbers for this value. | required
38 | render_offset: 16 # 32 / 64 / 128 etc...
39 |
40 | tool_items:
41 | example_tool:
42 | name: test_pickaxe
43 | type: pickaxe # pickaxe | axe | shovel | sword | hoe - required
44 | tier: diamond # diamond | golden | iron | stone | wooden - required
45 | texture_path: apple # required
46 | mining_speed: 3.0 # use only float numbers for this value. | required
47 | durability: 100 # required
48 | attack_point: 2 # required
49 | render_offset: 16 # 32 / 64 / 128 etc...
50 |
51 | armor_items:
52 | example_helmet:
53 | name: test_helmet
54 | type: helmet # helmet | chestplate | leggings | boots - required
55 | texture_path: iron_helmet
56 | defense_points: 5 # required
57 | durability: 500 # required
58 | render_offset: 16 # 32 / 64 / 128 etc...
--------------------------------------------------------------------------------
/src/Customies.php:
--------------------------------------------------------------------------------
1 | getDataFolder() . "idcache"));
33 | $provider = new WritableWorldProviderManagerEntry(\Closure::fromCallable([LevelDB::class, 'isValid']), fn(string $path) => new LevelDB($path), Closure::fromCallable([LevelDB::class, 'generate']));
34 | $this->getServer()->getWorldManager()->getProviderManager()->addProvider($provider, "leveldb", true);
35 | $this->getServer()->getWorldManager()->getProviderManager()->setDefault($provider);
36 | }
37 |
38 | protected function onEnable(): void {
39 | $this->getServer()->getPluginManager()->registerEvents(new CustomiesListener(), $this);
40 | $this->saveDefaultConfig();
41 |
42 | $allItemsConfig = $this->getConfig()->getAll();
43 |
44 | foreach ($allItemsConfig as $itemType => $items) {
45 | switch ($itemType) {
46 | case 'basic_items':
47 | foreach ($items as $itemName => $valuesBasic) {
48 | $itemName = $valuesBasic['name'];
49 | $textureName = $valuesBasic['texture_path'];
50 | $maxStackSize = $valuesBasic['max_stack_size'];
51 | $allowOffHand = $valuesBasic['allow_off_hand'] ?? false;
52 | $renderOffset = $valuesBasic['render_offset'];
53 | CustomiesItemFactory::getInstance()->registerItem(BasicItem::class, 'custom:' . strtolower(str_replace(' ', '_', $itemName)), $itemName, $textureName, $maxStackSize, $allowOffHand, $renderOffset);
54 | }
55 | break;
56 | case 'armor_items':
57 | foreach ($items as $itemName => $valuesArmor) {
58 | $itemName = $valuesArmor['name'];
59 | $type = $valuesArmor['type'];
60 | $defensePoints = $valuesArmor['defense_points'];
61 | $durability = $valuesArmor['durability'];
62 | $textureName = $valuesArmor['texture_path'];
63 | $renderOffset = $valuesArmor['render_offset'];
64 |
65 | switch ($type) {
66 | case "helmet":
67 | $slot = ArmorInventory::SLOT_HEAD;
68 | break;
69 | case "chestplate":
70 | $slot = ArmorInventory::SLOT_CHEST;
71 | break;
72 | case "leggings":
73 | $slot = ArmorInventory::SLOT_LEGS;
74 | break;
75 | case "boots":
76 | $slot = ArmorInventory::SLOT_FEET;
77 | break;
78 | }
79 |
80 |
81 | $info = new ArmorTypeInfo($defensePoints, $durability, $slot);
82 | CustomiesItemFactory::getInstance()->registerArmor(CustomArmor::class, 'custom:' . strtolower(str_replace(' ', '_', $itemName)), $itemName, $info, $textureName, $renderOffset);
83 | }
84 | break;
85 | case 'food_items':
86 | foreach ($items as $itemName => $valuesFood) {
87 | $itemName = (string)$valuesFood['name'];
88 | $textureName = (string)$valuesFood['texture_path'];
89 | $maxStackSize = (int)$valuesFood['max_stack_size'];
90 | $isPotion = (bool)$valuesFood['is_potion'];
91 | $canAlwaysEat = (bool)$valuesFood['can_always_eat'];
92 | $foodRestore = (int)$valuesFood['food_restore'];
93 | $saturationRestore = (float)$valuesFood['saturation_restore'];
94 | $renderOffset = (int)$valuesFood['render_offset'];
95 | CustomiesItemFactory::getInstance()->registerFood(FoodItem::class, 'custom:' . strtolower(str_replace(' ', '_', $itemName)), $itemName, $isPotion, $textureName, $maxStackSize, $canAlwaysEat, $foodRestore, $saturationRestore, $renderOffset);
96 | }
97 | break;
98 | case 'tool_items':
99 | foreach ($items as $itemName => $valuesTools) {
100 | $itemName = $valuesTools['name'];
101 | $textureName = $valuesTools['texture_path'];
102 | $type = $valuesTools['type'];
103 | $tier = $valuesTools['tier'];
104 | $speedMining = $valuesTools['mining_speed'];
105 | $durability = $valuesTools['durability'];
106 | $attack = $valuesTools['attack_point'];
107 | $renderOffset = $valuesTools['render_offset'];
108 |
109 | switch ($tier) {
110 | case 'diamond':
111 | default:
112 | $tier = ToolTier::DIAMOND();
113 | break;
114 | case 'golden':
115 | $tier = ToolTier::GOLD();
116 | break;
117 | case 'iron':
118 | $tier = ToolTier::IRON();
119 | break;
120 | case 'stone':
121 | $tier = ToolTier::STONE();
122 | break;
123 | case 'wooden':
124 | $tier = ToolTier::WOOD();
125 | break;
126 | }
127 |
128 | switch ($type) {
129 | case 'pickaxe':
130 | $class = PickaxeItem::class;
131 | break;
132 | case 'axe':
133 | $class = AxeItem::class;
134 | break;
135 | case 'shovel':
136 | $class = ShovelItem::class;
137 | break;
138 | case 'sword':
139 | $class = SwordItem::class;
140 | break;
141 | case 'hoe':
142 | $class = HoeItem::class;
143 | break;
144 | }
145 | CustomiesItemFactory::getInstance()->registerTool($class, 'custom:' . strtolower(str_replace(' ', '_', $itemName)), $itemName, $durability, $speedMining, $textureName, $attack, $renderOffset, $tier);
146 | }
147 | break;
148 | }
149 | }
150 |
151 | $cachePath = $this->getDataFolder() . "idcache";
152 | $this->getScheduler()->scheduleDelayedTask(new ClosureTask(static function () use ($cachePath): void {
153 | // This task is scheduled with a 0-tick delay so it runs as soon as the server has started. Plugins should
154 | // register their custom blocks and entities in onEnable() before this is executed.
155 | Cache::getInstance()->save();
156 | CustomiesBlockFactory::getInstance()->registerCustomRuntimeMappings();
157 | CustomiesBlockFactory::getInstance()->addWorkerInitHook($cachePath);
158 | }), 0);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/CustomiesListener.php:
--------------------------------------------------------------------------------
1 | experiments = new Experiments([
36 | // "data_driven_items" is required for custom blocks to render in-game. With this disabled, they will be
37 | // shown as the UPDATE texture block.
38 | "data_driven_items" => true,
39 | ], true);
40 | }
41 |
42 | public function onPlayerCreate(PlayerCreationEvent $event): void {
43 | $event->setPlayerClass(CustomPlayer::class);
44 | }
45 |
46 | public function onDataPacketSend(DataPacketSendEvent $event): void {
47 | foreach($event->getPackets() as $packet){
48 | if($packet instanceof BiomeDefinitionListPacket) {
49 | // ItemComponentPacket needs to be sent after the BiomeDefinitionListPacket.
50 | if($this->cachedItemComponentPacket === null) {
51 | // Wait for the data to be needed before it is actually cached. Allows for all blocks and items to be
52 | // registered before they are cached for the rest of the runtime.
53 | $this->cachedItemComponentPacket = ItemComponentPacket::create(CustomiesItemFactory::getInstance()->getItemComponentEntries());
54 | }
55 | foreach($event->getTargets() as $session){
56 | $session->sendDataPacket($this->cachedItemComponentPacket);
57 | }
58 | } elseif($packet instanceof StartGamePacket) {
59 | if(count($this->cachedItemTable) === 0) {
60 | // Wait for the data to be needed before it is actually cached. Allows for all blocks and items to be
61 | // registered before they are cached for the rest of the runtime.
62 | $this->cachedItemTable = array_merge($packet->itemTable, CustomiesItemFactory::getInstance()->getItemTableEntries());
63 | $this->cachedBlockPalette = CustomiesBlockFactory::getInstance()->getBlockPaletteEntries();
64 | }
65 | $packet->levelSettings->experiments = $this->experiments;
66 | $packet->itemTable = $this->cachedItemTable;
67 | $packet->blockPalette = $this->cachedBlockPalette;
68 | } else if($packet instanceof ResourcePackStackPacket) {
69 | $packet->experiments = $this->experiments;
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/block/BlockPalette.php:
--------------------------------------------------------------------------------
1 | runtimeBlockMapping = $instance = RuntimeBlockMapping::getInstance();
27 | $this->states = $instance->getBedrockKnownStates();
28 | $runtimeBlockMapping = new ReflectionClass($instance);
29 | $this->bedrockKnownStates = $bedrockKnownStates = $runtimeBlockMapping->getProperty("bedrockKnownStates");
30 | $bedrockKnownStates->setAccessible(true);
31 | }
32 |
33 | /**
34 | * @return CompoundTag[]
35 | */
36 | public function getStates(): array {
37 | return $this->states;
38 | }
39 |
40 | /**
41 | * @return R12ToCurrentBlockMapEntry[]
42 | */
43 | public function getCustomStates(): array {
44 | return $this->customStates;
45 | }
46 |
47 | /**
48 | * Inserts the provided state in to the correct position of the palette.
49 | */
50 | public function insertState(CompoundTag $state, int $meta = 0): void {
51 | if($state->getString("name") === "") {
52 | throw new RuntimeException("Block state must contain a StringTag called 'name'");
53 | }
54 | if($state->getCompoundTag("states") === null) {
55 | throw new RuntimeException("Block state must contain a CompoundTag called 'states'");
56 | }
57 | $this->sortWith($state);
58 | $this->customStates[] = new R12ToCurrentBlockMapEntry($state->getString("name"), $meta, $state);
59 | }
60 |
61 | /**
62 | * Sorts the palette's block states in the correct order, also adding the provided state to the array.
63 | */
64 | private function sortWith(CompoundTag $newState): void {
65 | // To sort the block palette we first have to split the palette up in to groups of states. We only want to sort
66 | // using the name of the block, and keeping the order of the existing states.
67 | $states = [];
68 | foreach($this->states as $state){
69 | $states[$state->getString("name")][] = $state;
70 | }
71 | // Append the new state we are sorting with at the end to preserve existing order.
72 | $states[$newState->getString("name")][] = $newState;
73 |
74 | $names = array_keys($states);
75 | // As of 1.18.30, blocks are sorted using a fnv164 hash of their names.
76 | usort($names, static fn(string $a, string $b) => strcmp(hash("fnv164", $a), hash("fnv164", $b)));
77 | $sortedStates = [];
78 | foreach($names as $name){
79 | // With the sorted list of names, we can now go back and add all the states for each block in the correct order.
80 | foreach($states[$name] as $state){
81 | $sortedStates[] = $state;
82 | }
83 | }
84 | $this->states = $sortedStates;
85 | $this->bedrockKnownStates->setValue($this->runtimeBlockMapping, $sortedStates);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/block/CustomiesBlockFactory.php:
--------------------------------------------------------------------------------
1 |
50 | */
51 | private array $blockFuncs = [];
52 | /** @var BlockPaletteEntry[] */
53 | private array $blockPaletteEntries = [];
54 | /** @var R12ToCurrentBlockMapEntry[] */
55 | private array $legacyStateMap = [];
56 |
57 | public function __construct() {
58 | $this->increaseBlockFactoryLimits();
59 | }
60 |
61 | /**
62 | * Modifies the properties in the BlockFactory instance to increase the SplFixedArrays to double the limit of blocks
63 | * that can be registered.
64 | */
65 | public function increaseBlockFactoryLimits(): void {
66 | $instance = BlockFactory::getInstance();
67 | $blockFactory = new ReflectionClass($instance);
68 | foreach(["fullList", "mappedStateIds"] as $propertyName){
69 | $property = $blockFactory->getProperty($propertyName);
70 | $property->setAccessible(true);
71 | /** @var SplFixedArray $array */
72 | $array = $property->getValue($instance);
73 | $array->setSize(self::NEW_BLOCK_FACTORY_SIZE);
74 | $property->setValue($instance, $array);
75 | }
76 | $instance->light = SplFixedArray::fromArray(array_merge($instance->light->toArray(), array_fill(count($instance->light), self::NEW_BLOCK_FACTORY_SIZE, 0)));
77 | $instance->lightFilter = SplFixedArray::fromArray(array_merge($instance->lightFilter->toArray(), array_fill(count($instance->lightFilter), self::NEW_BLOCK_FACTORY_SIZE, 1)));
78 | $instance->blocksDirectSkyLight = SplFixedArray::fromArray(array_merge($instance->blocksDirectSkyLight->toArray(), array_fill(count($instance->blocksDirectSkyLight), self::NEW_BLOCK_FACTORY_SIZE, false)));
79 | $instance->blastResistance = SplFixedArray::fromArray(array_merge($instance->blastResistance->toArray(), array_fill(count($instance->blastResistance), self::NEW_BLOCK_FACTORY_SIZE, 0.0)));
80 | }
81 |
82 | /**
83 | * Adds a worker initialize hook to the async pool to sync the BlockFactory for every thread worker that is created.
84 | * It is especially important for the workers that deal with chunk encoding, as using the wrong runtime ID mappings
85 | * can result in massive issues with almost every block showing as the wrong thing and causing lag to clients.
86 | */
87 | public function addWorkerInitHook(string $cachePath): void {
88 | $server = Server::getInstance();
89 | $blocks = $this->blockFuncs;
90 | $server->getAsyncPool()->addWorkerStartHook(static function (int $worker) use ($cachePath, $server, $blocks): void {
91 | $server->getAsyncPool()->submitTaskToWorker(new AsyncRegisterBlocksTask($cachePath, $blocks), $worker);
92 | });
93 | }
94 |
95 | /**
96 | * Get a custom block from its identifier. An exception will be thrown if the block is not registered.
97 | */
98 | public function get(string $identifier): Block {
99 | $id = LegacyBlockIdToStringIdMap::getInstance()->stringToLegacy($identifier) ?? -1;
100 | if($id < 0) {
101 | throw new InvalidArgumentException("Custom block " . $identifier . " is not registered");
102 | }
103 |
104 | return BlockFactory::getInstance()->get($id, 0);
105 | }
106 |
107 | /**
108 | * Returns all the block palette entries that need to be sent to the client.
109 | * @return BlockPaletteEntry[]
110 | */
111 | public function getBlockPaletteEntries(): array {
112 | return $this->blockPaletteEntries;
113 | }
114 |
115 | /**
116 | * Register a block to the BlockFactory and all the required mappings.
117 | * @phpstan-param (Closure(int): Block) $blockFunc
118 | */
119 | public function registerBlock(Closure $blockFunc, string $identifier, ?Model $model = null, ?CreativeInventoryInfo $creativeInfo = null): void {
120 | $id = $this->getNextAvailableId($identifier);
121 | $block = $blockFunc($id);
122 | if(!$block instanceof Block) {
123 | throw new InvalidArgumentException("Class returned from closure is not a Block");
124 | }
125 |
126 | if(BlockFactory::getInstance()->isRegistered($id)) {
127 | throw new InvalidArgumentException("Block with ID " . $id . " is already registered");
128 | }
129 | BlockFactory::getInstance()->register($block);
130 | CustomiesItemFactory::getInstance()->registerBlockItem($identifier, $block);
131 |
132 | $propertiesTag = CompoundTag::create();
133 | $components = CompoundTag::create()
134 | ->setTag("minecraft:light_emission", CompoundTag::create()
135 | ->setByte("emission", $block->getLightLevel()))
136 | ->setTag("minecraft:block_light_filter", CompoundTag::create()
137 | ->setByte("lightLevel", $block->getLightFilter()))
138 | ->setTag("minecraft:destructible_by_mining", CompoundTag::create()
139 | ->setFloat("value", $block->getBreakInfo()->getHardness()))
140 | ->setTag("minecraft:destructible_by_explosion", CompoundTag::create()
141 | ->setFloat("value", $block->getBreakInfo()->getBlastResistance()))
142 | ->setTag("minecraft:friction", CompoundTag::create()
143 | ->setFloat("value", $block->getFrictionFactor()))
144 | ->setTag("minecraft:flammable", CompoundTag::create()
145 | ->setInt("catch_chance_modifier", $block->getFlameEncouragement())
146 | ->setInt("destroy_chance_modifier", $block->getFlammability()));
147 |
148 | if($model !== null) {
149 | foreach($model->toNBT() as $tagName => $tag){
150 | $components->setTag($tagName, $tag);
151 | }
152 | }
153 |
154 | if($block instanceof Permutable) {
155 | $blockPropertyNames = $blockPropertyValues = $blockProperties = [];
156 | foreach($block->getBlockProperties() as $blockProperty){
157 | $blockPropertyNames[] = $blockProperty->getName();
158 | $blockPropertyValues[] = $blockProperty->getValues();
159 | $blockProperties[] = $blockProperty->toNBT();
160 | }
161 | $permutations = array_map(static fn(Permutation $permutation) => $permutation->toNBT(), $block->getPermutations());
162 |
163 | // The 'minecraft:on_player_placing' component is required for the client to predict block placement, making
164 | // it a smoother experience for the end-user.
165 | $components->setTag("minecraft:on_player_placing", CompoundTag::create());
166 | $propertiesTag->setTag("permutations", new ListTag($permutations));
167 | $propertiesTag->setTag("properties", new ListTag($blockProperties));
168 |
169 | foreach(Permutations::getCartesianProduct($blockPropertyValues) as $meta => $permutations){
170 | // We need to insert states for every possible permutation to allow for all blocks to be used and to
171 | // keep in sync with the client's block palette.
172 | $states = CompoundTag::create();
173 | foreach($permutations as $i => $value){
174 | $states->setTag($blockPropertyNames[$i], NBT::getTagType($value));
175 | }
176 | $blockState = CompoundTag::create()
177 | ->setString("name", $identifier)
178 | ->setTag("states", $states);
179 | BlockPalette::getInstance()->insertState($blockState, $meta);
180 | }
181 | } else {
182 | // If a block does not contain any permutations we can just insert the one state.
183 | $blockState = CompoundTag::create()
184 | ->setString("name", $identifier)
185 | ->setTag("states", CompoundTag::create());
186 | BlockPalette::getInstance()->insertState($blockState);
187 | }
188 |
189 | $creativeInfo ??= CreativeInventoryInfo::DEFAULT();
190 | $components->setTag("minecraft:creative_category", CompoundTag::create()
191 | ->setString("category", $creativeInfo->getCategory())
192 | ->setString("group", $creativeInfo->getGroup()));
193 | $propertiesTag->setTag("components", $components);
194 | $propertiesTag->setTag("menu_category", CompoundTag::create()
195 | ->setString("category", $creativeInfo?->getCategory() ?? "")
196 | ->setString("group", $creativeInfo?->getGroup() ?? ""));
197 | $propertiesTag->setInt("molangVersion", 1);
198 | CreativeInventory::getInstance()->add($block->asItem());
199 |
200 | $this->blockPaletteEntries[] = new BlockPaletteEntry($identifier, new CacheableNbt($propertiesTag));
201 | $this->blockFuncs[$identifier] = $blockFunc;
202 | LegacyBlockIdToStringIdMap::getInstance()->registerMapping($identifier, $id);
203 | }
204 |
205 | /**
206 | * Registers the custom block runtime mappings to tell PocketMine about the custom blocks.
207 | */
208 | public function registerCustomRuntimeMappings(): void {
209 | $instance = RuntimeBlockMapping::getInstance();
210 | $runtimeBlockMapping = new ReflectionClass($instance);
211 |
212 | foreach(["legacyToRuntimeMap", "runtimeToLegacyMap"] as $propertyName){
213 | $property = $runtimeBlockMapping->getProperty($propertyName);
214 | $property->setAccessible(true);
215 | $property->setValue($instance, []);
216 | }
217 |
218 | $registerMappingMethod = $runtimeBlockMapping->getMethod("registerMapping");
219 | $registerMappingMethod->setAccessible(true);
220 | $registerMapping = $registerMappingMethod->getClosure($instance);
221 | if($registerMapping === null) {
222 | throw new RuntimeException("Unable to access mapping registration");
223 | }
224 |
225 | $legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
226 | /** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
227 | $legacyStateMap = [];
228 |
229 | $legacyStateMapReader = PacketSerializer::decoder((string)file_get_contents(BEDROCK_DATA_PATH . "r12_to_current_block_map.bin"), 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()));
230 | $nbtReader = new NetworkNbtSerializer();
231 | while(!$legacyStateMapReader->feof()){
232 | $id = $legacyStateMapReader->getString();
233 | $meta = $legacyStateMapReader->getLShort();
234 |
235 | $offset = $legacyStateMapReader->getOffset();
236 | $state = $nbtReader->read($legacyStateMapReader->getBuffer(), $offset)->mustGetCompoundTag();
237 | $legacyStateMapReader->setOffset($offset);
238 | $legacyStateMap[] = new R12ToCurrentBlockMapEntry($id, $meta, $state);
239 | }
240 |
241 | foreach(BlockPalette::getInstance()->getCustomStates() as $state){
242 | $legacyStateMap[] = $state;
243 | }
244 |
245 | /**
246 | * @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
247 | */
248 | $idToStatesMap = [];
249 | $states = BlockPalette::getInstance()->getStates();
250 | foreach($states as $k => $state){
251 | $idToStatesMap[$state->getString("name")][] = $k;
252 | }
253 |
254 | foreach($legacyStateMap as $pair){
255 | $id = $legacyIdMap->stringToLegacy($pair->getId());
256 | if($id === null) {
257 | throw new RuntimeException("No legacy ID matches " . $pair->getId());
258 | }
259 | $data = $pair->getMeta();
260 | if($data > 15) {
261 | continue;
262 | }
263 | $mappedState = $pair->getBlockState();
264 | $mappedName = $mappedState->getString("name");
265 | if(!isset($idToStatesMap[$mappedName])) {
266 | continue;
267 | }
268 | foreach($idToStatesMap[$mappedName] as $k){
269 | $networkState = $states[$k];
270 | if($mappedState->equals($networkState)) {
271 | $registerMapping($k, $id, $data);
272 | continue 2;
273 | }
274 | }
275 | }
276 | }
277 |
278 | /**
279 | * Returns the next available custom block id, an exception will be thrown if the block factory is full.
280 | */
281 | private function getNextAvailableId(string $identifier): int {
282 | $id = Cache::getInstance()->getNextAvailableBlockID($identifier);
283 | if($id > (self::NEW_BLOCK_FACTORY_SIZE / 16)) {
284 | throw new OutOfRangeException("All custom block ids are used up");
285 | }
286 | return $id;
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/src/block/Material.php:
--------------------------------------------------------------------------------
1 | target = $target;
31 | $this->texture = $texture;
32 | $this->renderMethod = $renderMethod;
33 | $this->faceDimming = $faceDimming;
34 | $this->ambientOcclusion = $ambientOcclusion;
35 | }
36 |
37 | /**
38 | * Returns the targeted face for the material.
39 | */
40 | public function getTarget(): string {
41 | return $this->target;
42 | }
43 |
44 | /**
45 | * Returns the material in the correct NBT format supported by the client.
46 | */
47 | public function toNBT(): CompoundTag {
48 | return CompoundTag::create()
49 | ->setString("texture", $this->texture)
50 | ->setString("render_method", $this->renderMethod)
51 | ->setByte("face_dimming", $this->faceDimming ? 1 : 0)
52 | ->setByte("ambient_occlusion", $this->ambientOcclusion ? 1 : 0);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/block/Model.php:
--------------------------------------------------------------------------------
1 | materials = $materials;
24 | $this->geometry = $geometry;
25 | $this->origin = $origin;
26 | $this->size = $size;
27 | }
28 |
29 | /**
30 | * Returns the model in the correct NBT format supported by the client.
31 | * @return CompoundTag[]
32 | */
33 | public function toNBT(): array {
34 | $materials = CompoundTag::create();
35 | foreach($this->materials as $material){
36 | $materials->setTag($material->getTarget(), $material->toNBT());
37 | }
38 |
39 | return [
40 | "minecraft:material_instances" => CompoundTag::create()
41 | ->setTag("mappings", CompoundTag::create()) // What is this? The client will crash if it is not sent.
42 | ->setTag("materials", $materials),
43 | "minecraft:geometry" => CompoundTag::create()
44 | ->setString("value", $this->geometry),
45 | "minecraft:collision_box" => CompoundTag::create()
46 | ->setByte("enabled", 1)
47 | ->setTag("origin", new ListTag([
48 | new FloatTag($this->origin->getX()),
49 | new FloatTag($this->origin->getY()),
50 | new FloatTag($this->origin->getZ())
51 | ]))
52 | ->setTag("size", new ListTag([
53 | new FloatTag($this->size->getX()),
54 | new FloatTag($this->size->getY()),
55 | new FloatTag($this->size->getZ())
56 | ])),
57 | "minecraft:selection_box" => CompoundTag::create()
58 | ->setByte("enabled", 1)
59 | ->setTag("origin", new ListTag([
60 | new FloatTag($this->origin->getX()),
61 | new FloatTag($this->origin->getY()),
62 | new FloatTag($this->origin->getZ())
63 | ]))
64 | ->setTag("size", new ListTag([
65 | new FloatTag($this->size->getX()),
66 | new FloatTag($this->size->getY()),
67 | new FloatTag($this->size->getZ())
68 | ]))
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/block/permutations/BlockProperty.php:
--------------------------------------------------------------------------------
1 | name = $name;
18 | $this->values = $values;
19 | }
20 |
21 | /**
22 | * Returns the name of the block property provided in the constructor.
23 | */
24 | public function getName(): string {
25 | return $this->name;
26 | }
27 |
28 | /**
29 | * Returns the array of possible values of the block property provided in the constructor.
30 | */
31 | public function getValues(): array {
32 | return $this->values;
33 | }
34 |
35 | /**
36 | * Returns the block property in the correct NBT format supported by the client.
37 | */
38 | public function toNBT(): CompoundTag {
39 | $values = array_map(static fn($value) => NBT::getTagType($value), $this->values);
40 | return CompoundTag::create()
41 | ->setString("name", $this->name)
42 | ->setTag("enum", new ListTag($values));
43 | }
44 | }
--------------------------------------------------------------------------------
/src/block/permutations/Permutable.php:
--------------------------------------------------------------------------------
1 | condition = $condition;
16 | $this->components = CompoundTag::create();
17 | }
18 |
19 | /**
20 | * Returns the permutation with the provided component added to the current list of components.
21 | */
22 | public function withComponent(string $component, mixed $value) : self {
23 | $this->components->setTag($component, NBT::getTagType($value));
24 | return $this;
25 | }
26 |
27 | /**
28 | * Returns the permutation in the correct NBT format supported by the client.
29 | */
30 | public function toNBT(): CompoundTag {
31 | return CompoundTag::create()
32 | ->setString("condition", $this->condition)
33 | ->setTag("components", $this->components);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/block/permutations/Permutations.php:
--------------------------------------------------------------------------------
1 | $blockProperty->getValues(), $block->getBlockProperties());
22 | $properties = self::getCartesianProduct($possibleValues)[$meta] ?? null;
23 | if($properties === null) {
24 | throw new InvalidBlockStateException("Unable to calculate permutations from block meta: " . $meta);
25 | }
26 | return $properties;
27 | }
28 |
29 | /**
30 | * Attempts to convert the block in to a meta value based on the possible permutations of the block. An exception is
31 | * thrown if the state of the block is not a possible combination of all the block properties.
32 | */
33 | public static function toMeta(Permutable $block): int {
34 | $possibleValues = array_map(static fn(BlockProperty $blockProperty) => $blockProperty->getValues(), $block->getBlockProperties());
35 | foreach(self::getCartesianProduct($possibleValues) as $meta => $permutations){
36 | if($permutations === $block->getCurrentBlockProperties()) {
37 | return $meta;
38 | }
39 | }
40 | throw new InvalidBlockStateException("Unable to calculate block meta from current permutations");
41 | }
42 |
43 | /**
44 | * Returns the number of bits required to represent all the possible permutations of the block.
45 | */
46 | public static function getStateBitmask(Permutable $block): int {
47 | $possibleValues = array_map(static fn(BlockProperty $blockProperty) => $blockProperty->getValues(), $block->getBlockProperties());
48 | return count(self::getCartesianProduct($possibleValues)) - 1;
49 | }
50 |
51 | /**
52 | * Returns an 2-dimensional array containing all possible combinations of the provided arrays using the cartesian
53 | * product (https://en.wikipedia.org/wiki/Cartesian_product).
54 | */
55 | public static function getCartesianProduct(array $arrays): array {
56 | $result = [];
57 | $count = count($arrays) - 1;
58 | $combinations = array_product(array_map(static fn(array $array) => count($array), $arrays));
59 | for($i = 0; $i < $combinations; $i++){
60 | $result[] = array_map(static fn(array $array) => current($array), $arrays);
61 | for($j = $count; $j >= 0; $j--){
62 | if(next($arrays[$j])) {
63 | break;
64 | }
65 | reset($arrays[$j]);
66 | }
67 | }
68 | return $result;
69 | }
70 | }
--------------------------------------------------------------------------------
/src/block/permutations/RotatableTrait.php:
--------------------------------------------------------------------------------
1 | withComponent("minecraft:rotation", CompoundTag::create()
34 | ->setFloat("x", 0)
35 | ->setFloat("y", 0)
36 | ->setFloat("z", 0)),
37 | (new Permutation("q.block_property('customies:rotation') == 3"))
38 | ->withComponent("minecraft:rotation", CompoundTag::create()
39 | ->setFloat("x", 0)
40 | ->setFloat("y", 180)
41 | ->setFloat("z", 0)),
42 | (new Permutation("q.block_property('customies:rotation') == 4"))
43 | ->withComponent("minecraft:rotation", CompoundTag::create()
44 | ->setFloat("x", 0)
45 | ->setFloat("y", 90)
46 | ->setFloat("z", 0)),
47 | (new Permutation("q.block_property('customies:rotation') == 5"))
48 | ->withComponent("minecraft:rotation", CompoundTag::create()
49 | ->setFloat("x", 0)
50 | ->setFloat("y", 270)
51 | ->setFloat("z", 0)),
52 | ];
53 | }
54 |
55 | public function getCurrentBlockProperties(): array {
56 | return [$this->facing];
57 | }
58 |
59 | protected function writeStateToMeta(): int {
60 | return Permutations::toMeta($this);
61 | }
62 |
63 | public function readStateFromData(int $id, int $stateMeta): void {
64 | $blockProperties = Permutations::fromMeta($this, $stateMeta);
65 | $this->facing = $blockProperties[0] ?? Facing::NORTH;
66 | }
67 |
68 | public function getStateBitmask(): int {
69 | return Permutations::getStateBitmask($this);
70 | }
71 |
72 | public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null): bool {
73 | if($player !== null) {
74 | $this->facing = $player->getHorizontalFacing();
75 | }
76 | return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
77 | }
78 | }
--------------------------------------------------------------------------------
/src/entity/CustomiesEntityFactory.php:
--------------------------------------------------------------------------------
1 | $className
26 | * @phpstan-param Closure(World $world, CompoundTag $nbt) : Entity $creationFunc
27 | */
28 | public function registerEntity(string $className, string $identifier, ?Closure $creationFunc = null, string $behaviourId = ""): void {
29 | EntityFactory::getInstance()->register($className, $creationFunc ?? static function (World $world, CompoundTag $nbt) use ($className): Entity {
30 | return new $className(EntityDataHelper::parseLocation($nbt, $world), $nbt);
31 | }, [$identifier]);
32 | $this->updateStaticPacketCache($identifier, $behaviourId);
33 | }
34 |
35 | private function updateStaticPacketCache(string $identifier, string $behaviourId): void {
36 | $instance = StaticPacketCache::getInstance();
37 | $staticPacketCache = new ReflectionClass($instance);
38 | $property = $staticPacketCache->getProperty("availableActorIdentifiers");
39 | $property->setAccessible(true);
40 | /** @var AvailableActorIdentifiersPacket $packet */
41 | $packet = $property->getValue($instance);
42 | /** @var CompoundTag $root */
43 | $root = $packet->identifiers->getRoot();
44 | $idList = $root->getListTag("idlist") ?? new ListTag();
45 | $idList->push(CompoundTag::create()
46 | ->setString("id", $identifier)
47 | ->setString("bid", $behaviourId));
48 | $packet->identifiers = new CacheableNbt($root);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/item/CreativeInventoryInfo.php:
--------------------------------------------------------------------------------
1 | category = $category;
102 | $this->group = $group;
103 | }
104 |
105 | /**
106 | * Returns the category the item is part of.
107 | */
108 | public function getCategory(): string {
109 | return $this->category;
110 | }
111 |
112 | /**
113 | * Returns the numeric representation of the category the item is part of.
114 | */
115 | public function getNumericCategory(): int {
116 | return match ($this->getCategory()) {
117 | self::CATEGORY_CONSTRUCTION => 1,
118 | self::CATEGORY_NATURE => 2,
119 | self::CATEGORY_EQUIPMENT => 3,
120 | self::CATEGORY_ITEMS => 4,
121 | default => 0
122 | };
123 | }
124 |
125 | /**
126 | * Returns the group the item is part of, if any.
127 | */
128 | public function getGroup(): string {
129 | return $this->group;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/item/CustomiesItemFactory.php:
--------------------------------------------------------------------------------
1 | itemTableEntries[$identifier] ?? null)?->getNumericId();
43 | if($id === null) {
44 | throw new InvalidArgumentException("Custom item " . $identifier . " is not registered");
45 | }
46 | return ItemFactory::getInstance()->get($id, 0, $amount);
47 | }
48 |
49 | /**
50 | * Returns the item properties CompoundTag which maps out all custom item properties.
51 | * @return ItemComponentPacketEntry[]
52 | */
53 | public function getItemComponentEntries(): array {
54 | return $this->itemComponentEntries;
55 | }
56 |
57 | /**
58 | * Returns custom item entries for the StartGamePacket itemTable property.
59 | * @return ItemTypeEntry[]
60 | */
61 | public function getItemTableEntries(): array {
62 | return array_values($this->itemTableEntries);
63 | }
64 |
65 | /**
66 | * Registers the item to the item factory and assigns it an ID. It also updates the required mappings and stores the
67 | * item components if present.
68 | * @phpstan-param class-string $className
69 | */
70 | public function registerItem(string $className, string $identifier, string $name, string $texture,int $maxStackSize, bool $allowOffHand, int $renderOffset): void {
71 | if($className !== Item::class) {
72 | Utils::testValidInstance($className, Item::class);
73 | }
74 |
75 | /** @var Item $item */
76 | $item = new $className(new ItemIdentifier(Cache::getInstance()->getNextAvailableItemID($identifier), 0), $name, $texture, $allowOffHand, $maxStackSize, $renderOffset);
77 |
78 | if(ItemFactory::getInstance()->isRegistered($item->getId())) {
79 | throw new RuntimeException("Item with ID " . $item->getId() . " is already registered");
80 | }
81 | $this->registerCustomItemMapping($item->getId());
82 | ItemFactory::getInstance()->register($item);
83 |
84 | if(($componentBased = $item instanceof ItemComponents)) {
85 | $componentsTag = $item->getComponents();
86 | $componentsTag->setInt("id", $item->getId());
87 | $componentsTag->setString("name", $identifier);
88 | $this->itemComponentEntries[$identifier] = new ItemComponentPacketEntry($identifier, new CacheableNbt($componentsTag));
89 | }
90 |
91 | $this->itemTableEntries[$identifier] = new ItemTypeEntry($identifier, $item->getId(), $componentBased);
92 | CreativeInventory::getInstance()->add($item);
93 | }
94 |
95 | public function registerArmor(string $className, string $identifier, string $name,ArmorTypeInfo $info, string $texture, int $renderOffset): void {
96 | if($className !== Item::class) {
97 | Utils::testValidInstance($className, Item::class);
98 | }
99 |
100 | /** @var Item $item */
101 | $item = new $className(new ItemIdentifier(Cache::getInstance()->getNextAvailableItemID($identifier), 0), $name, $info, $texture, $renderOffset);
102 |
103 | if(ItemFactory::getInstance()->isRegistered($item->getId())) {
104 | throw new RuntimeException("Item with ID " . $item->getId() . " is already registered");
105 | }
106 | $this->registerCustomItemMapping($item->getId());
107 | ItemFactory::getInstance()->register($item);
108 |
109 | if(($componentBased = $item instanceof ItemComponents)) {
110 | $componentsTag = $item->getComponents();
111 | $componentsTag->setInt("id", $item->getId());
112 | $componentsTag->setString("name", $identifier);
113 | $this->itemComponentEntries[$identifier] = new ItemComponentPacketEntry($identifier, new CacheableNbt($componentsTag));
114 | }
115 |
116 | $this->itemTableEntries[$identifier] = new ItemTypeEntry($identifier, $item->getId(), $componentBased);
117 | CreativeInventory::getInstance()->add($item);
118 | }
119 |
120 |
121 | public function registerFood(string $className, string $identifier, string $name, bool $isPotion, string $texture, int $maxStackSize, bool $canAlwaysEat, int $foodRestore, float $saturationRestore, int $renderOffset): void {
122 | if($className !== Item::class) {
123 | Utils::testValidInstance($className, Item::class);
124 | }
125 |
126 | /** @var Item $item */
127 | $item = new $className(new ItemIdentifier(Cache::getInstance()->getNextAvailableItemID($identifier), 0), $name, $isPotion, $texture, $maxStackSize, $canAlwaysEat, $foodRestore, $saturationRestore, $renderOffset);
128 |
129 | if(ItemFactory::getInstance()->isRegistered($item->getId())) {
130 | throw new RuntimeException("Item with ID " . $item->getId() . " is already registered");
131 | }
132 | $this->registerCustomItemMapping($item->getId());
133 | ItemFactory::getInstance()->register($item);
134 |
135 | if(($componentBased = $item instanceof ItemComponents)) {
136 | $componentsTag = $item->getComponents();
137 | $componentsTag->setInt("id", $item->getId());
138 | $componentsTag->setString("name", $identifier);
139 | $this->itemComponentEntries[$identifier] = new ItemComponentPacketEntry($identifier, new CacheableNbt($componentsTag));
140 | }
141 |
142 | $this->itemTableEntries[$identifier] = new ItemTypeEntry($identifier, $item->getId(), $componentBased);
143 | CreativeInventory::getInstance()->add($item);
144 | }
145 |
146 |
147 | public function registerTool(string $className, string $identifier, string $name, int $durability, float $efficiency, string $textureName, int $attack, int $renderOffset, ToolTier $tier): void {
148 | if($className !== Item::class) {
149 | Utils::testValidInstance($className, Item::class);
150 | }
151 |
152 | /** @var Item $item */
153 | $item = new $className(new ItemIdentifier(Cache::getInstance()->getNextAvailableItemID($identifier), 0), $name, $durability, $efficiency, $textureName, $attack, $renderOffset, $tier);
154 |
155 | if(ItemFactory::getInstance()->isRegistered($item->getId())) {
156 | throw new RuntimeException("Item with ID " . $item->getId() . " is already registered");
157 | }
158 | $this->registerCustomItemMapping($item->getId());
159 | ItemFactory::getInstance()->register($item);
160 |
161 | if(($componentBased = $item instanceof ItemComponents)) {
162 | $componentsTag = $item->getComponents();
163 | $componentsTag->setInt("id", $item->getId());
164 | $componentsTag->setString("name", $identifier);
165 | $this->itemComponentEntries[$identifier] = new ItemComponentPacketEntry($identifier, new CacheableNbt($componentsTag));
166 | }
167 |
168 | $this->itemTableEntries[$identifier] = new ItemTypeEntry($identifier, $item->getId(), $componentBased);
169 | CreativeInventory::getInstance()->add($item);
170 | }
171 |
172 | /**
173 | * Registers a custom item ID to the required mappings in the ItemTranslator instance.
174 | */
175 | private function registerCustomItemMapping(int $id): void {
176 | $translator = ItemTranslator::getInstance();
177 | $reflection = new ReflectionClass($translator);
178 |
179 | $reflectionProperty = $reflection->getProperty("simpleCoreToNetMapping");
180 | $reflectionProperty->setAccessible(true);
181 | /** @var int[] $value */
182 | $value = $reflectionProperty->getValue($translator);
183 | $reflectionProperty->setValue($translator, $value + [$id => $id]);
184 |
185 | $reflectionProperty = $reflection->getProperty("simpleNetToCoreMapping");
186 | $reflectionProperty->setAccessible(true);
187 | /** @var int[] $value */
188 | $value = $reflectionProperty->getValue($translator);
189 | $reflectionProperty->setValue($translator, $value + [$id => $id]);
190 | }
191 |
192 | /**
193 | * Registers the required mappings for the block to become an item that can be placed etc. It is assigned an ID that
194 | * correlates to its block ID.
195 | */
196 | public function registerBlockItem(string $identifier, Block $block): void {
197 | $itemId = $block->getIdInfo()->getItemId();
198 | $this->registerCustomItemMapping($itemId);
199 | $this->itemTableEntries[] = new ItemTypeEntry($identifier, $itemId, false);
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/item/ItemComponents.php:
--------------------------------------------------------------------------------
1 | components[$component->getName()] = $component;
42 | }
43 |
44 | public function hasComponent(string $name): bool {
45 | return isset($this->components[$name]);
46 | }
47 |
48 | public function getComponents(): CompoundTag {
49 | $components = CompoundTag::create();
50 | $properties = CompoundTag::create();
51 | foreach($this->components as $component){
52 | $tag = NBT::getTagType($component->getValue());
53 | if($tag === null) {
54 | throw new RuntimeException("Failed to get tag type for component " . $component->getName());
55 | }
56 | if($component->isProperty()) {
57 | $properties->setTag($component->getName(), $tag);
58 | continue;
59 | }
60 | $components->setTag($component->getName(), $tag);
61 | }
62 | $components->setTag("item_properties", $properties);
63 | return CompoundTag::create()
64 | ->setTag("components", $components);
65 | }
66 |
67 | /**
68 | * Initializes the item with default components that are required for the item to function correctly.
69 | */
70 | protected function initComponent(string $texture, ?CreativeInventoryInfo $creativeInfo = null): void {
71 | $creativeInfo ??= CreativeInventoryInfo::DEFAULT();
72 | $this->addComponent(new CreativeCategoryComponent($creativeInfo));
73 | $this->addComponent(new CreativeGroupComponent($creativeInfo));
74 | $this->addComponent(new IconComponent($texture));
75 | $this->addComponent(new MaxStackSizeComponent($this->getMaxStackSize()));
76 |
77 | if($this instanceof Armor) {
78 | $slot = match ($this->getArmorSlot()) {
79 | ArmorInventory::SLOT_HEAD => WearableComponent::SLOT_ARMOR_HEAD,
80 | ArmorInventory::SLOT_CHEST => WearableComponent::SLOT_ARMOR_CHEST,
81 | ArmorInventory::SLOT_LEGS => WearableComponent::SLOT_ARMOR_LEGS,
82 | ArmorInventory::SLOT_FEET => WearableComponent::SLOT_ARMOR_FEET,
83 | default => WearableComponent::SLOT_ARMOR
84 | };
85 | $this->addComponent(new ArmorComponent($this->getDefensePoints()));
86 | $this->addComponent(new WearableComponent($slot));
87 | }
88 |
89 | if($this instanceof Consumable) {
90 | if(($food = $this instanceof Food)) {
91 | $this->addComponent(new FoodComponent(!$this->requiresHunger()));
92 | }
93 | $this->addComponent(new UseAnimationComponent($food ? UseAnimationComponent::ANIMATION_EAT : UseAnimationComponent::ANIMATION_DRINK));
94 | $this->setUseDuration(20);
95 | }
96 |
97 | if($this instanceof Durable) {
98 | $this->addComponent(new DurabilityComponent($this->getMaxDurability()));
99 | }
100 |
101 | if($this instanceof ProjectileItem) {
102 | $this->addComponent(new ProjectileComponent("projectile"));
103 | $this->addComponent(new ThrowableComponent(true));
104 | }
105 |
106 | if($this->getName() !== "Unknown") {
107 | $this->addComponent(new DisplayNameComponent($this->getName()));
108 | }
109 |
110 | if($this->getFuelTime() > 0) {
111 | $this->addComponent(new FuelComponent($this->getFuelTime()));
112 | }
113 | }
114 |
115 | /**
116 | * When a custom item has a texture that is not 16x16, the item will scale when held in a hand based on the size of
117 | * the texture. This method adds the minecraft:render_offsets component with the correct data for the provided width
118 | * and height of a texture to make the item scale correctly. An optional bool for hand equipped can be used if the
119 | * item is something like a tool or weapon.
120 | */
121 | protected function setupRenderOffsets(int $width, int $height, bool $handEquipped = false): void {
122 | $this->addComponent(new HandEquippedComponent($handEquipped));
123 | $this->addComponent(new RenderOffsetsComponent($width, $height, $handEquipped));
124 | }
125 |
126 | /**
127 | * Change if you want to allow the item to be placed in a player's off-hand or not. This is set to false by default,
128 | * so it only needs to be set if you want to allow it.
129 | */
130 | protected function allowOffHand(bool $offHand = true): void {
131 | $this->addComponent(new AllowOffHandComponent($offHand));
132 | }
133 |
134 | /**
135 | * Set the number of seconds the item should be on cooldown for after being used. By default, the cooldown category
136 | * will be the name of the item, but to share the cooldown across multiple items you can provide a shared category.
137 | */
138 | protected function setUseCooldown(float $duration, string $category = ""): void {
139 | $this->addComponent(new CooldownComponent($category !== "" ? $category : $this->getName(), $duration));
140 | }
141 |
142 | /**
143 | * Set the number of ticks the use animation should play for before consuming the item. There are 20 ticks in a
144 | * second, so providing the number 20 will create a duration of 1 second.
145 | */
146 | protected function setUseDuration(int $ticks): void {
147 | $this->addComponent(new UseDurationComponent($ticks));
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/item/component/AllowOffHandComponent.php:
--------------------------------------------------------------------------------
1 | offHand = $offHand;
12 | }
13 |
14 | public function getName(): string {
15 | return "allow_off_hand";
16 | }
17 |
18 | public function getValue(): bool {
19 | return $this->offHand;
20 | }
21 |
22 | public function isProperty(): bool {
23 | return true;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/item/component/ArmorComponent.php:
--------------------------------------------------------------------------------
1 | protection = $protection;
23 | $this->textureType = $textureType;
24 | }
25 |
26 | public function getName(): string {
27 | return "minecraft:armor";
28 | }
29 |
30 | public function getValue(): array {
31 | return [
32 | "protection" => $this->protection,
33 | "texture_type" => $this->textureType
34 | ];
35 | }
36 |
37 | public function isProperty(): bool {
38 | return false;
39 | }
40 | }
--------------------------------------------------------------------------------
/src/item/component/ChargeableComponent.php:
--------------------------------------------------------------------------------
1 | movementModifier = $movementModifier;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:chargeable";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "movement_modifier" => $this->movementModifier
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/CooldownComponent.php:
--------------------------------------------------------------------------------
1 | category = $category;
13 | $this->duration = $duration;
14 | }
15 |
16 | public function getName(): string {
17 | return "minecraft:cooldown";
18 | }
19 |
20 | public function getValue(): array {
21 | return [
22 | "category" => $this->category,
23 | "duration" => $this->duration
24 | ];
25 | }
26 |
27 | public function isProperty(): bool {
28 | return false;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/item/component/CreativeCategoryComponent.php:
--------------------------------------------------------------------------------
1 | creativeInfo = $creativeInfo;
14 | }
15 |
16 | public function getName(): string {
17 | return "creative_category";
18 | }
19 |
20 | public function getValue(): int {
21 | return $this->creativeInfo->getNumericCategory();
22 | }
23 |
24 | public function isProperty(): bool {
25 | return true;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/CreativeGroupComponent.php:
--------------------------------------------------------------------------------
1 | creativeInfo = $creativeInfo;
14 | }
15 |
16 | public function getName(): string {
17 | return "creative_group";
18 | }
19 |
20 | public function getValue(): string {
21 | return $this->creativeInfo->getGroup();
22 | }
23 |
24 | public function isProperty(): bool {
25 | return true;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/DiggerComponent.php:
--------------------------------------------------------------------------------
1 | $this->destroySpeeds
22 | ];
23 | }
24 |
25 | public function isProperty(): bool {
26 | return false;
27 | }
28 |
29 | public function withBlocks(int $speed, Block ...$blocks): DiggerComponent {
30 | foreach($blocks as $block){
31 | $this->destroySpeeds[] = [
32 | "block" => [
33 | "name" => LegacyBlockIdToStringIdMap::getInstance()->legacyToString($block->getId())
34 | ],
35 | "speed" => $speed
36 | ];
37 | }
38 | return $this;
39 | }
40 |
41 | public function withTags(int $speed, string ...$tags): DiggerComponent {
42 | $query = implode(",", array_map(fn($tag) => "'" . $tag . "'", $tags));
43 | $this->destroySpeeds[] = [
44 | "block" => [
45 | "tags" => "query.any_tag(" . $query . ")"
46 | ],
47 | "speed" => $speed
48 | ];
49 | return $this;
50 | }
51 | }
--------------------------------------------------------------------------------
/src/item/component/DisplayNameComponent.php:
--------------------------------------------------------------------------------
1 | name = $name;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:display_name";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "value" => $this->name
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/DurabilityComponent.php:
--------------------------------------------------------------------------------
1 | maxDurability = $maxDurability;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:durability";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "max_durability" => $this->maxDurability
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/FoilComponent.php:
--------------------------------------------------------------------------------
1 | foil = $foil;
12 | }
13 |
14 | public function getName(): string {
15 | return "foil";
16 | }
17 |
18 | public function getValue(): bool {
19 | return $this->foil;
20 | }
21 |
22 | public function isProperty(): bool {
23 | return true;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/item/component/FoodComponent.php:
--------------------------------------------------------------------------------
1 | canAlwaysEat = $canAlwaysEat;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:food";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "can_always_eat" => $this->canAlwaysEat
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/FuelComponent.php:
--------------------------------------------------------------------------------
1 | duration = $duration;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:fuel";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "duration" => $this->duration
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/HandEquippedComponent.php:
--------------------------------------------------------------------------------
1 | handEquipped = $handEquipped;
12 | }
13 |
14 | public function getName(): string {
15 | return "hand_equipped";
16 | }
17 |
18 | public function getValue(): bool {
19 | return $this->handEquipped;
20 | }
21 |
22 | public function isProperty(): bool {
23 | return true;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/item/component/IconComponent.php:
--------------------------------------------------------------------------------
1 | texture = $texture;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:icon";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "texture" => $this->texture
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return true;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/ItemComponent.php:
--------------------------------------------------------------------------------
1 | protection = $protection;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:knockback_resistance";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "protection" => $this->protection
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/MaxStackSizeComponent.php:
--------------------------------------------------------------------------------
1 | maxStackSize = $maxStackSize;
12 | }
13 |
14 | public function getName(): string {
15 | return "max_stack_size";
16 | }
17 |
18 | public function getValue(): int {
19 | return $this->maxStackSize;
20 | }
21 |
22 | public function isProperty(): bool {
23 | return true;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/item/component/ProjectileComponent.php:
--------------------------------------------------------------------------------
1 | projectileEntity = $projectileEntity;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:projectile";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "projectile_entity" => $this->projectileEntity
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/RenderOffsetsComponent.php:
--------------------------------------------------------------------------------
1 | textureWidth = $textureWidth;
14 | $this->textureHeight = $textureHeight;
15 | $this->handEquipped = $handEquipped;
16 | }
17 |
18 | public function getName(): string {
19 | return "minecraft:render_offsets";
20 | }
21 |
22 | public function getValue(): array {
23 | $horizontal = ($this->handEquipped ? 0.075 : 0.1) / ($this->textureWidth / 16);
24 | $vertical = ($this->handEquipped ? 0.125 : 0.1) / ($this->textureHeight / 16);
25 | $perspectives = [
26 | "first_person" => [
27 | "scale" => [$horizontal, $vertical, $horizontal],
28 | ],
29 | "third_person" => [
30 | "scale" => [$horizontal, $vertical, $horizontal]
31 | ]
32 | ];
33 | return [
34 | "main_hand" => $perspectives,
35 | "off_hand" => $perspectives
36 | ];
37 | }
38 |
39 | public function isProperty(): bool {
40 | return false;
41 | }
42 | }
--------------------------------------------------------------------------------
/src/item/component/ThrowableComponent.php:
--------------------------------------------------------------------------------
1 | doSwingAnimation = $doSwingAnimation;
12 | }
13 |
14 | public function getName(): string {
15 | return "minecraft:throwable";
16 | }
17 |
18 | public function getValue(): array {
19 | return [
20 | "do_swing_animation" => $this->doSwingAnimation
21 | ];
22 | }
23 |
24 | public function isProperty(): bool {
25 | return false;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/item/component/UseAnimationComponent.php:
--------------------------------------------------------------------------------
1 | animation = $animation;
15 | }
16 |
17 | public function getName(): string {
18 | return "use_animation";
19 | }
20 |
21 | public function getValue(): int {
22 | return $this->animation;
23 | }
24 |
25 | public function isProperty(): bool {
26 | return true;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/item/component/UseDurationComponent.php:
--------------------------------------------------------------------------------
1 | duration = $duration;
12 | }
13 |
14 | public function getName(): string {
15 | return "use_duration";
16 | }
17 |
18 | public function getValue(): int {
19 | return $this->duration;
20 | }
21 |
22 | public function isProperty(): bool {
23 | return true;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/item/component/WearableComponent.php:
--------------------------------------------------------------------------------
1 | slot = $slot;
27 | }
28 |
29 | public function getName(): string {
30 | return "minecraft:wearable";
31 | }
32 |
33 | public function getValue(): array {
34 | return [
35 | "slot" => $this->slot
36 | ];
37 | }
38 |
39 | public function isProperty(): bool {
40 | return false;
41 | }
42 | }
--------------------------------------------------------------------------------
/src/item/types/AxeItem.php:
--------------------------------------------------------------------------------
1 | durability = $durability;
27 | $this->efficiency = $efficiency;
28 | $this->attack = $attack;
29 | parent::__construct($identifier, $name, $tier);
30 |
31 |
32 |
33 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_EQUIPMENT, CreativeInventoryInfo::GROUP_AXE);
34 | $this->initComponent($textureName, $inventory);
35 | $this->addComponent(new DurabilityComponent($durability));
36 | $this->addComponent(new HandEquippedComponent());
37 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, true));
38 | }
39 |
40 | public function getMaxDurability(): int
41 | {
42 | return $this->durability;
43 | }
44 |
45 | public function getBaseMiningEfficiency(): float
46 | {
47 | return $this->efficiency;
48 | }
49 |
50 | public function getAttackPoints(): int
51 | {
52 | return $this->attack;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/item/types/BasicItem.php:
--------------------------------------------------------------------------------
1 | maxStackSize = $maxStackSize;
21 | parent::__construct($identifier, $name);
22 |
23 | $inventory = CreativeInventoryInfo::DEFAULT();
24 | $this->initComponent($texture, $inventory);
25 | $this->addComponent(new AllowOffHandComponent($allowOffHand));
26 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, false));
27 |
28 | }
29 |
30 | public function getMaxStackSize(): int{
31 | return $this->maxStackSize;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/item/types/CustomArmor.php:
--------------------------------------------------------------------------------
1 | getArmorSlot()) {
25 | case ArmorInventory::SLOT_HEAD:
26 | $wearable = WearableComponent::SLOT_ARMOR_HEAD;
27 | $group = CreativeInventoryInfo::GROUP_HELMET;
28 | break;
29 | case ArmorInventory::SLOT_CHEST:
30 | $wearable = WearableComponent::SLOT_ARMOR_CHEST;
31 | $group = CreativeInventoryInfo::GROUP_CHESTPLATE;
32 | break;
33 | case ArmorInventory::SLOT_LEGS:
34 | $wearable = WearableComponent::SLOT_ARMOR_LEGS;
35 | $group = CreativeInventoryInfo::GROUP_LEGGINGS;
36 | break;
37 | case ArmorInventory::SLOT_FEET:
38 | $wearable = WearableComponent::SLOT_ARMOR_FEET;
39 | $group = CreativeInventoryInfo::GROUP_BOOTS;
40 | break;
41 | }
42 |
43 | $inventory = new CreativeInventoryInfo(
44 | CreativeInventoryInfo::CATEGORY_EQUIPMENT,
45 | $group ?? CreativeInventoryInfo::DEFAULT()
46 | );
47 |
48 | parent::__construct($identifier, $name, $info);
49 |
50 |
51 | $this->initComponent($texture,$inventory);
52 |
53 | $this->addComponent(new ArmorComponent($this->getDefensePoints(), "diamond"));
54 | $this->addComponent(new DurabilityComponent($this->getMaxDurability()));
55 | $this->addComponent(new WearableComponent($wearable ?? WearableComponent::SLOT_ARMOR));
56 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, false));
57 |
58 | }
59 | }
--------------------------------------------------------------------------------
/src/item/types/FoodItem.php:
--------------------------------------------------------------------------------
1 | canAlwaysEat = $canAlwaysEat;
27 | $this->maxStackSize = $maxStackSize;
28 | $this->foodRestore = $foodRestore;
29 | $this->saturationRestore = $saturationRestore;
30 |
31 | parent::__construct($identifier, $name);
32 |
33 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_NATURE, CreativeInventoryInfo::GROUP_MISC_FOOD);
34 | $this->initComponent($texture, $inventory);
35 | $this->addComponent(new FoodComponent($canAlwaysEat));
36 | $this->addComponent(new UseDurationComponent(32));
37 | $this->addComponent(new UseAnimationComponent($isPotion ? UseAnimationComponent::ANIMATION_DRINK : UseAnimationComponent::ANIMATION_EAT));
38 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, false));
39 | }
40 |
41 | public function getFoodRestore(): int
42 | {
43 | return $this->foodRestore;
44 | }
45 |
46 | public function getSaturationRestore(): float
47 | {
48 | return $this->saturationRestore;
49 | }
50 |
51 | public function getMaxStackSize(): int
52 | {
53 | return $this->maxStackSize;
54 | }
55 | }
--------------------------------------------------------------------------------
/src/item/types/HoeItem.php:
--------------------------------------------------------------------------------
1 | durability = $durability;
30 | $this->efficiency = $efficiency;
31 | $this->attack = $attack;
32 | parent::__construct($identifier, $name, $tier);
33 |
34 |
35 |
36 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_EQUIPMENT, CreativeInventoryInfo::GROUP_HOE);
37 | $this->initComponent($textureName, $inventory);
38 | $this->addComponent(new DurabilityComponent($durability));
39 | $this->addComponent(new HandEquippedComponent());
40 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, true));
41 | }
42 |
43 | public function getMaxDurability(): int
44 | {
45 | return $this->durability;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/item/types/PickaxeItem.php:
--------------------------------------------------------------------------------
1 | durability = $durability;
26 | $this->efficiency = $efficiency;
27 | $this->attack = $attack;
28 | parent::__construct($identifier, $name, $tier);
29 |
30 |
31 |
32 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_EQUIPMENT, CreativeInventoryInfo::GROUP_PICKAXE);
33 | $this->initComponent($textureName, $inventory);
34 | $this->addComponent(new DurabilityComponent($durability));
35 | $this->addComponent(new HandEquippedComponent());
36 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, true));
37 | }
38 |
39 | public function getMaxDurability(): int
40 | {
41 | return $this->durability;
42 | }
43 |
44 | public function getBaseMiningEfficiency(): float
45 | {
46 | return $this->efficiency;
47 | }
48 |
49 | public function getAttackPoints(): int
50 | {
51 | return $this->attack;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/item/types/ShovelItem.php:
--------------------------------------------------------------------------------
1 | durability = $durability;
28 | $this->efficiency = $efficiency;
29 | $this->attack = $attack;
30 | parent::__construct($identifier, $name, $tier);
31 |
32 |
33 |
34 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_EQUIPMENT, CreativeInventoryInfo::GROUP_SHOVEL);
35 | $this->initComponent($textureName, $inventory);
36 | $this->addComponent(new DurabilityComponent($durability));
37 | $this->addComponent(new HandEquippedComponent());
38 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, true));
39 | }
40 |
41 | public function getMaxDurability(): int
42 | {
43 | return $this->durability;
44 | }
45 |
46 | public function getBaseMiningEfficiency(): float
47 | {
48 | return $this->efficiency;
49 | }
50 |
51 | public function getAttackPoints(): int
52 | {
53 | return $this->attack;
54 | }
55 | }
--------------------------------------------------------------------------------
/src/item/types/SwordItem.php:
--------------------------------------------------------------------------------
1 | durability = $durability;
28 | $this->efficiency = $efficiency;
29 | $this->attack = $attack;
30 | parent::__construct($identifier, $name, $tier);
31 |
32 |
33 |
34 | $inventory = new CreativeInventoryInfo(CreativeInventoryInfo::CATEGORY_EQUIPMENT, CreativeInventoryInfo::GROUP_SWORD);
35 | $this->initComponent($textureName, $inventory);
36 | $this->addComponent(new DurabilityComponent($durability));
37 | $this->addComponent(new HandEquippedComponent());
38 | $this->addComponent(new RenderOffsetsComponent($renderOffset, $renderOffset, true));
39 | }
40 |
41 | public function getMaxDurability(): int
42 | {
43 | return $this->durability;
44 | }
45 |
46 | public function getAttackPoints(): int
47 | {
48 | return $this->attack;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/player/CustomPlayer.php:
--------------------------------------------------------------------------------
1 | blockBreakHandlerCustom?->update() ?: $this->blockBreakHandlerCustom = null;
21 | return parent::onUpdate($currentTick);
22 | }
23 |
24 | public function attackBlock(Vector3 $pos, int $face): bool
25 | {
26 | if ($pos->distanceSquared($this->location) > 10000) {
27 | return false;
28 | }
29 | $target = $this->getWorld()->getBlock($pos);
30 |
31 | $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
32 |
33 | if ($this->isSpectator()) {
34 | $ev->cancel();
35 | }
36 |
37 | $ev->call();
38 | if ($ev->isCancelled()) {
39 | return false;
40 | }
41 | $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
42 | if ($target->onAttack($this->inventory->getItemInHand(), $face, $this)) {
43 | return true;
44 | }
45 | $block = $target->getSide($face);
46 | if ($block->getTypeId() === VanillaBlocks::FIRE()->getTypeId()) {
47 | $this->getWorld()->setBlock($block->getPosition(), VanillaBlocks::AIR());
48 | $this->getWorld()->addSound($block->getPosition()->add(0.5, 0.5, 0.5), new FireExtinguishSound());
49 | return true;
50 | }
51 |
52 |
53 | if (!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()) {
54 | $this->blockBreakHandlerCustom = new SurvivalBlockHandler($this, $pos, $target, $face, 16);
55 | }
56 |
57 | return true;
58 | }
59 |
60 | public function continueBreakBlock(Vector3 $pos, int $face): void
61 | {
62 | if ($this->blockBreakHandlerCustom !== null && $this->blockBreakHandlerCustom->getBlockPos()->distanceSquared($pos) < 0.0001) {
63 | $this->blockBreakHandlerCustom->setTargetedFace($face);
64 | $this->blockBreakHandlerCustom->setTargetedFace($face);
65 | if (($this->blockBreakHandlerCustom->getBreakProgress() + $this->blockBreakHandlerCustom->getBreakSpeed()) >= 0.80) {
66 | $pos = $this->blockBreakHandlerCustom->getBlockPos();
67 | $this->breakBlock($pos);
68 | }
69 | }
70 | }
71 |
72 | public function breakBlock(Vector3 $pos): bool
73 | {
74 | $this->removeCurrentWindow();
75 | if ($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7)) {
76 | $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
77 | $this->stopBreakBlock($pos);
78 | $item = $this->inventory->getItemInHand();
79 | $oldItem = clone $item;
80 | if ($this->getWorld()->useBreakOn($pos, $item, $this, true)) {
81 | if ($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->inventory->getItemInHand())) {
82 | if ($item instanceof Durable && $item->isBroken()) {
83 | $this->broadcastSound(new ItemBreakSound());
84 | }
85 | $this->inventory->setItemInHand($item);
86 | }
87 | $this->hungerManager->exhaust(0.005, PlayerExhaustEvent::CAUSE_MINING);
88 | return true;
89 | }
90 | } else {
91 | $this->logger->debug("Cancelled block break at $pos due to not currently being interactable");
92 | }
93 |
94 | return false;
95 | }
96 |
97 |
98 | # BLOCK HANDLER
99 |
100 | public function stopBreakBlock(Vector3 $pos): void
101 | {
102 | if ($this->blockBreakHandlerCustom !== null && $this->blockBreakHandlerCustom->getBlockPos()->distanceSquared($pos) < 0.0001) {
103 | $this->blockBreakHandlerCustom = null;
104 | }
105 | }
106 |
107 | protected function destroyCycles(): void
108 | {
109 | parent::destroyCycles();
110 | $this->blockBreakHandlerCustom = null;
111 | }
112 | }
--------------------------------------------------------------------------------
/src/player/SurvivalBlockHandler.php:
--------------------------------------------------------------------------------
1 | breakSpeed = $this->calculateBreakProgressPerTick();
56 | if ($this->breakSpeed > 0) {
57 | $this->player->getWorld()->broadcastPacketToViewers(
58 | $this->blockPos,
59 | LevelEventPacket::create(LevelEvent::BLOCK_START_BREAK, (int)(65535 * $this->breakSpeed), $this->blockPos)
60 | );
61 | }
62 | }
63 |
64 | /**
65 | * Returns the calculated break speed as percentage progress per game tick.
66 | */
67 | private function calculateBreakProgressPerTick(): float
68 | {
69 | if (!$this->block->getBreakInfo()->isBreakable()) {
70 | return 0.0;
71 | }
72 |
73 | /*
74 | switch ($this->block->getBreakInfo()->getToolType()) {
75 | case BlockToolType::PICKAXE:
76 | if ($this->player->getInventory()->getItemInHand() instanceof CustomPickaxe) {
77 | $breakTimePerTick = $this->block->getBreakInfo()->getHardness() * BlockBreakInfo::COMPATIBLE_TOOL_MULTIPLIER * 20;
78 | $efficiency = $this->player->getInventory()->getItemInHand()->getMiningEfficiency(true);
79 | $breakTimePerTick /= $efficiency;
80 | }
81 | break;
82 | case BlockToolType::AXE:
83 | if ($this->player->getInventory()->getItemInHand() instanceof CustomAxe) {
84 | $breakTimePerTick = $this->block->getBreakInfo()->getHardness() * BlockBreakInfo::COMPATIBLE_TOOL_MULTIPLIER * 20;
85 | $efficiency = $this->player->getInventory()->getItemInHand()->getMiningEfficiency(true);
86 | $breakTimePerTick /= $efficiency;
87 | }
88 | break;
89 | case BlockToolType::SHOVEL:
90 | if ($this->player->getInventory()->getItemInHand() instanceof CustomShovel) {
91 | $breakTimePerTick = $this->block->getBreakInfo()->getHardness() * BlockBreakInfo::COMPATIBLE_TOOL_MULTIPLIER * 20;
92 | $efficiency = $this->player->getInventory()->getItemInHand()->getMiningEfficiency(true);
93 | $breakTimePerTick /= $efficiency;
94 | }
95 | break;
96 | case BlockToolType::SWORD:
97 | if ($this->player->getInventory()->getItemInHand() instanceof CustomSword) {
98 | $breakTimePerTick = $this->block->getBreakInfo()->getHardness() * BlockBreakInfo::COMPATIBLE_TOOL_MULTIPLIER * 1.5;
99 | $efficiency = $this->player->getInventory()->getItemInHand()->getMiningEfficiency(true);
100 | $breakTimePerTick /= $efficiency;
101 | }
102 | }
103 | if ($this->player->getInventory()->getItemInHand() instanceof CustomPickaxe) {
104 | $breakTimePerTick = $this->block->getBreakInfo()->getHardness() * BlockBreakInfo::COMPATIBLE_TOOL_MULTIPLIER * 20;
105 | $efficiency = $this->player->getInventory()->getItemInHand()->getMiningEfficiency(true);
106 | $breakTimePerTick /= $efficiency;
107 | } */
108 |
109 | $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20;
110 |
111 |
112 | if ($breakTimePerTick > 0) {
113 | return 1 / $breakTimePerTick;
114 | }
115 | return 1;
116 | }
117 |
118 | public function update(): bool
119 | {
120 | if ($this->player->getPosition()->distanceSquared($this->blockPos->add(0.5, 0.5, 0.5)) > $this->maxPlayerDistance ** 2) {
121 | return false;
122 | }
123 |
124 | $newBreakSpeed = $this->calculateBreakProgressPerTick();
125 | if (abs($newBreakSpeed - $this->breakSpeed) > 0.0001) {
126 | $this->breakSpeed = $newBreakSpeed;
127 | }
128 |
129 | $this->breakProgress += $this->breakSpeed;
130 |
131 | if (($this->fxTicker++ % $this->fxTickInterval) === 0 && $this->breakProgress < 1) {
132 | $this->player->getWorld()->addParticle($this->blockPos, new BlockPunchParticle($this->block, $this->targetedFace));
133 | $this->player->getWorld()->addSound($this->blockPos, new BlockPunchSound($this->block));
134 | $this->player->broadcastAnimation(new ArmSwingAnimation($this->player), $this->player->getViewers());
135 | }
136 |
137 | return $this->breakProgress < 1;
138 | }
139 |
140 | public function getBlockPos(): Vector3
141 | {
142 | return $this->blockPos;
143 | }
144 |
145 | public function getTargetedFace(): int
146 | {
147 | return $this->targetedFace;
148 | }
149 |
150 | public function setTargetedFace(int $face): void
151 | {
152 | Facing::validate($face);
153 | $this->targetedFace = $face;
154 | }
155 |
156 | public function getBreakSpeed(): float
157 | {
158 | return $this->breakSpeed;
159 | }
160 |
161 | public function getBreakProgress(): float
162 | {
163 | return $this->breakProgress;
164 | }
165 |
166 | public function __destruct()
167 | {
168 | if ($this->player->getWorld()->isInLoadedTerrain($this->blockPos)) {
169 | $this->player->getWorld()->broadcastPacketToViewers(
170 | $this->blockPos,
171 | LevelEventPacket::create(LevelEvent::BLOCK_STOP_BREAK, 0, $this->blockPos)
172 | );
173 | }
174 | }
175 | }
--------------------------------------------------------------------------------
/src/task/AsyncRegisterBlocksTask.php:
--------------------------------------------------------------------------------
1 | $blockFuncs
21 | */
22 | public function __construct(private string $cachePath, array $blockFuncs) {
23 | $this->blockFuncs = new Threaded();
24 | foreach($blockFuncs as $identifier => $blockFunc){
25 | $this->blockFuncs[$identifier] = $blockFunc;
26 | }
27 | }
28 |
29 | public function onRun(): void {
30 | Cache::setInstance(new Cache($this->cachePath));
31 | foreach($this->blockFuncs as $identifier => $blockFunc){
32 | // We do not care about the model or creative inventory data in other threads since it is unused outside of
33 | // the main thread.
34 | CustomiesBlockFactory::getInstance()->registerBlock($blockFunc, $identifier);
35 | }
36 | CustomiesBlockFactory::getInstance()->registerCustomRuntimeMappings();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/util/Cache.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | private array $itemCache = [];
25 |
26 | /**
27 | * @var array
28 | */
29 | private array $blockCache = [];
30 | private string $file;
31 |
32 | public function __construct(string $cacheFile) {
33 | $this->file = $cacheFile;
34 | if(file_exists($cacheFile)) {
35 | $data = igbinary_unserialize(gzuncompress(file_get_contents($cacheFile)));
36 | $this->blockCache = $data["blocks"];
37 | $this->itemCache = $data["items"];
38 | }
39 | }
40 |
41 | /**
42 | * Returns the next available block id.
43 | */
44 | public function getNextAvailableBlockID(string $identifier): int {
45 | return $this->getNextAvailableID($identifier, $this->blockCache, self::FIRST_BLOCK_ID);
46 | }
47 |
48 | /**
49 | * Returns the next available item id.
50 | */
51 | public function getNextAvailableItemID(string $identifier): int {
52 | return $this->getNextAvailableID($identifier, $this->itemCache, self::FIRST_ITEM_ID);
53 | }
54 |
55 | /**
56 | * Returns the lowest valid id for that specific identifier if it hasn't already been cached. It
57 | * will then cache it. If the identifier has been cached it will return the associated cached numeric id.
58 | */
59 | private function getNextAvailableID(string $identifier, array &$cache, int $startID): int {
60 | // If the ID is cached, return it.
61 | if(isset($cache[$identifier])) {
62 | return $cache[$identifier];
63 | }
64 |
65 | $id = null;
66 | if(count($cache) > 0) {
67 | // To make use of the empty sections in the cache, we need to find the lowest available id.
68 | // Flip the array to have numeric ids as keys.
69 | $flipped = array_flip($cache);
70 | // Go through every number to find any empty ID.
71 | for($i = array_key_first($flipped) + 1, $iMax = array_key_last($flipped) + 1; $i <= $iMax; $i++){
72 | if(!isset($flipped[$i])) {
73 | $id = $i;
74 | break;
75 | }
76 | }
77 | }
78 | $cache[$identifier] = ($id ??= $startID);
79 | return $id;
80 | }
81 |
82 | /**
83 | * Flushes the cache to disk in the appropriate format.
84 | */
85 | public function save(): void {
86 | $data = ["items" => $this->itemCache, "blocks" => $this->blockCache];
87 | Filesystem::safeFilePutContents($this->file, gzcompress(igbinary_serialize($data)));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/util/NBT.php:
--------------------------------------------------------------------------------
1 | self::getArrayTag($type),
31 | is_bool($type) => new ByteTag($type ? 1 : 0),
32 | is_float($type) => new FloatTag($type),
33 | is_int($type) => new IntTag($type),
34 | is_string($type) => new StringTag($type),
35 | $type instanceof CompoundTag => $type,
36 | default => null,
37 | };
38 | }
39 |
40 | /**
41 | * Creates a Tag that is either a ListTag or CompoundTag based on the data types of the keys in the provided array.
42 | */
43 | private static function getArrayTag(array $array): Tag {
44 | if(array_keys($array) === range(0, count($array) - 1)) {
45 | return new ListTag(array_map(fn($value) => self::getTagType($value), $array));
46 | }
47 | $tag = CompoundTag::create();
48 | foreach($array as $key => $value){
49 | $tag->setTag($key, self::getTagType($value));
50 | }
51 | return $tag;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/world/LegacyBlockIdToStringIdMap.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | private array $legacyToString;
19 | /**
20 | * @var int[]
21 | * @phpstan-var array
22 | */
23 | private array $stringToLegacy;
24 |
25 | public function __construct() {
26 | /** @phpstan-var array $blockIdMap */
27 |
28 | if (VersionInfo::BASE_VERSION === "4.15.0") {
29 | $blockIdMap = json_decode((string)file_get_contents(BEDROCK_DATA_PATH . "block_id_map.json"), true);
30 |
31 | } else {
32 | $blockIdMap = json_decode((string)file_get_contents(PATH . "vendor/pocketmine/bedrock-block-upgrade-schema/block_legacy_id_map.json"), true);
33 | }
34 |
35 | $this->stringToLegacy = $blockIdMap;
36 | /** @phpstan-var array $flipped */
37 | $flipped = array_flip($this->stringToLegacy);
38 | $this->legacyToString = $flipped;
39 | }
40 |
41 | public function legacyToString(int $legacy): ?string {
42 | return $this->legacyToString[$legacy] ?? null;
43 | }
44 |
45 | public function stringToLegacy(string $string): ?int {
46 | return $this->stringToLegacy[$string] ?? null;
47 | }
48 |
49 | public function registerMapping(string $string, int $legacy): void {
50 | $this->legacyToString[$legacy] = $string;
51 | $this->stringToLegacy[$string] = $legacy;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/world/LevelDB.php:
--------------------------------------------------------------------------------
1 | put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
35 |
36 | $chunk = $chunkData->getChunk();
37 | if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)) {
38 | $subChunks = $chunk->getSubChunks();
39 | foreach($subChunks as $y => $subChunk){
40 | $key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
41 | if($subChunk->isEmptyAuthoritative()) {
42 | $write->delete($key);
43 | } else {
44 | $subStream = new BinaryStream();
45 | $subStream->putByte(self::CURRENT_LEVEL_SUBCHUNK_VERSION);
46 |
47 | $layers = $subChunk->getBlockLayers();
48 | $subStream->putByte(count($layers));
49 | foreach($layers as $blocks){
50 | $subStream->putByte($blocks->getBitsPerBlock() << 1);
51 | $subStream->put($blocks->getWordArray());
52 |
53 | $palette = $blocks->getPalette();
54 | $subStream->putLInt(count($palette));
55 | $tags = [];
56 | foreach($palette as $p){
57 | $tags[] = new TreeRoot(CompoundTag::create()
58 | ->setString("name", $idMap->legacyToString($p >> Block::INTERNAL_METADATA_BITS) ?? "minecraft:info_update")
59 | ->setInt("oldid", $p >> Block::INTERNAL_METADATA_BITS) //PM only (debugging), vanilla doesn't have this
60 | ->setShort("val", $p & Block::INTERNAL_METADATA_MASK));
61 | }
62 |
63 | $subStream->put((new LittleEndianNbtSerializer())->writeMultiple($tags));
64 | }
65 |
66 | $write->put($key, $subStream->getBuffer());
67 | }
68 | }
69 | }
70 |
71 | if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)) {
72 | $write->put($index . self::TAG_DATA_2D, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
73 | }
74 |
75 | $write->put($index . self::TAG_STATE_FINALISATION, chr(self::FINALISATION_DONE));
76 |
77 | $this->writeTags($chunkData->getTileNBT(), $index . self::TAG_BLOCK_ENTITY, $write);
78 | $this->writeTags($chunkData->getEntityNBT(), $index . self::TAG_ENTITY, $write);
79 |
80 | $write->delete($index . self::TAG_DATA_2D_LEGACY);
81 | $write->delete($index . self::TAG_LEGACY_TERRAIN);
82 |
83 | $this->db->write($write);
84 | }
85 |
86 | /**
87 | * This method is copied from pocketmine/world/format/io/leveldb/LevelDB.writeTags() since it is private.
88 | * @param CompoundTag[] $targets
89 | */
90 | private function writeTags(array $targets, string $index, \LevelDBWriteBatch $write): void {
91 | if(count($targets) > 0) {
92 | $nbt = new LittleEndianNbtSerializer();
93 | $write->put($index, $nbt->writeMultiple(array_map(fn(CompoundTag $tag) => new TreeRoot($tag), $targets)));
94 | } else {
95 | $write->delete($index);
96 | }
97 | }
98 |
99 | /**
100 | * deserializePaletted is copied from pocketmine/world/format/io/leveldb/LevelDB.deserializePaletted() but changes
101 | * the LegacyBlockIdToStringIdMap instance to support loading custom blocks from the world.
102 | */
103 | protected function deserializePaletted(BinaryStream $stream): PalettedBlockArray {
104 | $bitsPerBlock = $stream->getByte() >> 1;
105 |
106 | try {
107 | $words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
108 | } catch(InvalidArgumentException $e) {
109 | throw new CorruptedChunkException("Failed to deserialize paletted storage: " . $e->getMessage(), 0, $e);
110 | }
111 | $nbt = new LittleEndianNbtSerializer();
112 | $palette = [];
113 | $idMap = LegacyBlockIdToStringIdMap::getInstance();
114 | for($i = 0, $paletteSize = $stream->getLInt(); $i < $paletteSize; ++$i){
115 | $offset = $stream->getOffset();
116 |
117 | $tag = $nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
118 | $stream->setOffset($offset);
119 |
120 | $id = $idMap->stringToLegacy($tag->getString("name")) ?? BlockLegacyIds::INFO_UPDATE;
121 | $data = $tag->getShort("val");
122 | $palette[] = ($id << Block::INTERNAL_METADATA_BITS) | $data;
123 | }
124 |
125 | return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------