Compare commits

..

247 Commits

Author SHA1 Message Date
fdae689f87 Fixed a series of mistakes that prevented converting from regionized leveldb to a different format 2024-12-23 19:11:59 +00:00
8027c271fa Remove regionized leveldb 32x32 and 64x64
these produce such small file sizes on average that the DB logs would probably take up a significant fraction of the world's footprint.
My gut instinct is that 128 will probably be the sweet spot, as on average it should sit well below the threshold for level 3 compactions, and most worlds would likely fit into a single DB.
256 is probably not worthwhile, but might be worth trying.
2024-12-21 18:47:44 +00:00
6abc40bb83 ... 2024-12-21 18:13:59 +00:00
07cd2e5542 Apparently intdiv() is fucking stupid 2024-12-21 18:10:25 +00:00
5b435e783c stfu 2024-12-21 18:03:41 +00:00
1224055f66 Introduced a custom LevelDB impl with regions and better key format
This new impl (which is not loadable by vanilla) is targeted at very large worlds, which experience significant I/O performance issues due to a variety of issues described in #6580.

Two main changes are made in RegionizedLevelDB:
- First, multiple LevelDBs are used, which cover a fixed NxN segment of terrain, similar to Anvil in Java. However, there's no constraint on these region sizes. Several experimental sizes are supported by default in WorldProviderManager.
- Second, bigEndianLong(morton2d(chunkX, chunkZ)) is used for chunk keys instead of littleEndianInt(chunkX).littleEndianInt(chunkZ). This new scheme has much better cache locality than Mojang's version, which reduces overlap and costly DB compactions.

The following new provider options are available as a result of this change:
- custom-leveldb-regions-32
- custom-leveldb-regions-64
- custom-leveldb-regions-128
- custom-leveldb-regions-256

Smaller sizes will likely be less space-efficient, but will also probably have better performance.
Once a sweet spot is found, a default will be introduced.

Note that the different variations of custom-leveldb-regions-* are not cross-compatible.
Conversion between the different formats is necessary if you want to change formats.
2024-12-21 17:56:09 +00:00
306623e890 FormatConverter: do periodic GC
this reduces the risk of OOM during conversion of large worlds
we probably ought to limit the size of region caches for regionized worlds, but that's a problem for another time.
2024-12-21 17:49:03 +00:00
ada3acdba4 FormatConverter: ensure we don't get stalled due to stdout buffer flood
this can happen due to very noisy outputs during conversion, e.g. if there were many unknown blocks.
2024-12-21 17:49:03 +00:00
6a1d80e021 tools/convert-world: fixed some UI issues 2024-12-21 17:49:02 +00:00
1ff0988e75 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12440895736
2024-12-21 01:22:54 +00:00
506cfe0362 Bump build/php from 5016e0a to b1eaaa4 (#6579)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `5016e0a` to `b1eaaa4`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](5016e0a3d5...b1eaaa48ec)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-20 10:39:39 +00:00
fea17fa4a9 RakLibServer: disable GC
GC is not required for RakLib as it doesn't generate any unmanaged cycles.
Cycles in general do exist (e.g. Server <-> ServerSession), but these are
explicitly cleaned up, so GC wouldn't have any useful work to do.
2024-12-19 20:33:40 +00:00
aee358d329 This timings handler management is a crap design
This has bitten me on the ass so many times now
2024-12-16 03:11:07 +00:00
80899ea72c Make sure timings are counted under the proper parents 2024-12-15 21:34:16 +00:00
42f90e94ff AsyncWorker now manually triggers GC at the end of each task run, similar to the main thread
this avoids costly GC runs during hot code.
2024-12-15 21:25:32 +00:00
8f536e6f21 always the CS 2024-12-15 20:46:10 +00:00
45482e868d Fixed AsyncWorker GC not getting re-enabled after memory dump
async workers still use automatic GC for now. We should probably switch to manual GC at some point, but it's not a priority right now.
2024-12-15 20:45:51 +00:00
742aa46b88 Separate memory dumping utilities from MemoryManager 2024-12-15 20:44:00 +00:00
cf1b360a62 World: Prevent block cache from getting too big
This has been a long-standing issue since at least 2016, and probably longer.
Heavy use of getBlock(At) could cause the cache to blow up and use all available memory.

Recently, it's become clear that unmanaged cache size is also a problem for GC, because
the large number of objects blows up the GC root buffer. At first, this causes more frequent
GC runs; later, the frequency of GC runs drops, but the performance cost of them goes up
substantially because of the sheer number of objects. We can avoid this by trimming the
cache when we detect that it's exceeded limits.

I've implemented this in such a way that failing to update blockCacheSize in new code
won't have lasting impacts, since the cache count will be recalculated during scheduled
cache cleaning anyway.

Closes #152.
2024-12-15 18:40:32 +00:00
0aa6cde259 Remove stupid MemoryManager settings
No one in their right mind is going to change the defaults for these anyway.

All this crap does is overwhelm users with stuff they don't understand.
Most of this stuff has no business being modified by non-developers anyway.
2024-12-15 16:41:54 +00:00
8f8fe948c1 MemoryManager: Control when cycle garbage collection is run (#6554)
This PR replicates the mechanism by which PHP's own GC is triggered: using a dynamically adjusted threshold based on the number of roots and the number of destroyed cycles. This approach was chosen to minimize behavioural changes.

This currently only applies to the main thread. Doing this for other threads is a bit more complicated (and in the case of RakLib, possibly not necessary anyway).

By doing this, we can get more accurate performance profiling. Instead of GC happening in random pathways and throwing off GC numbers, we trigger it in a predictable place, where timings can record it.

This change may also produce minor performance improvements in code touching lots of objects (such as `CraftingDataPacket` encoding`), which previously might've triggered multiple GC runs within a single tick. Now that GC runs wait for `MemoryManager`, it can touch as many objects as it wants during a tick without paying a performance penalty.

While working on this change I came across a number of issues that should probably be addressed in the future:

1) Objects like Server, World and Player that can't possibly be GC'd repeatedly end up in the GC root buffer because the refcounts fluctuate frequently. Because of the dependency chains in these objects, they all drag each other into GC, causing an almost guaranteed parasitic performance cost to GC. This is discussed in php/php-src#17131, as the proper solution to this is probably generational GC, or perhaps some way to explicitly mark objects to be ignored by GC.
2) World's `blockCache` blows up the GC root threshold due to poor size management. This leads to infrequent, but extremely expensive GC runs due to the sheer number of objects being scanned. We could avoid a lot of this cost by managing caches like this more effectively.
3) StringToItemParser and many of the pocketmine\data classes which make heavy use of closures are afflicted by thousands of reference cycles. This doesn't present a major performance issue in most cases because the cycles are simple, but this could easily be fixed with some simple refactors.
2024-12-15 16:26:39 +00:00
b10caf7437 Remove tool tier of some blocks to match vanilla (#6573) 2024-12-13 21:54:48 +00:00
de66d84d29 Implement new 1.20 and 1.21 records (#6572) 2024-12-13 21:10:34 +03:00
636f562bcf Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12307996607
2024-12-13 01:40:42 +00:00
42094e6768 Implement resin blocks & items (#6571) 2024-12-12 23:21:41 +03:00
b341078765 Implement new pale oak blocks (#6570) 2024-12-12 17:53:52 +03:00
f7687af337 Fixed draft release being created on release publish 2024-12-12 13:44:15 +00:00
ba93665fe7 TextFormat: reduce hella duplicated code in toHTML() 2024-12-10 14:11:11 +00:00
6817215683 TextFormat: Added new material colours for armor trims (#5838)
Unfortunately, these new formatting codes conflict with the Java strikethrough and underline, so we can't support these anymore.

A TextFormat::javaToBedrock() is provided to strip these codes, or (if these formats become supported via different codes) to convert them to Bedrock variants.

Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-12-10 13:40:03 +00:00
67b9d6222d 5.23.3 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12240364052
2024-12-09 16:52:50 +00:00
6f197bc1bb 5.23.2 (#6569) 2024-12-09 16:51:41 +00:00
bba525da02 Remove dead PHPStan ignored errors 2024-12-09 16:44:25 +00:00
ad6d34f1a6 Remove legacy make-release script
we no longer use this release workflow, all releases should now be done via pull request
2024-12-09 16:44:07 +00:00
a8eaa43bc8 Recombine release workflows
having two different workflows able to trigger releases is a pain for build number continuity.
perhaps longer term we should source the build number a different way, but these workflows needed restructuring anyway.
2024-12-09 16:36:26 +00:00
fe7c282052 Bump pocketmine/locale-data in the production-patch-updates group (#6568) 2024-12-09 12:03:09 +00:00
45917d495c Fixed CrashDump incorrectly detecting phar core crashes as plugin crashes (#6564)
fixes #6563

Since #6217 was merged, \pocketmine\PATH no longer includes the path of the original phar.
This means that the frame originating from the phar stub would not get its path cleaned up,
leading to it being incorrectly detected as a plugin frame.

We should probably explore better methods of detecting plugin crashes in the future; however
this fix should solve the immediate issue.
2024-12-08 16:52:33 +00:00
1481977f35 Create pr-stale.yml 2024-12-05 20:47:46 +00:00
8efdf501ad 5.23.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12187209543
2024-12-05 20:05:36 +00:00
2b0daebc2a 5.23.1 (#6562) 2024-12-05 20:04:43 +00:00
6b2da15b80 Fixed signs 2024-12-05 19:58:52 +00:00
2ef02a2c5e Upgraded block consistency check to detect tile changes 2024-12-05 19:57:13 +00:00
15e8895e54 5.23.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12183301507
2024-12-05 15:52:16 +00:00
ea8f971287 Release 5.23.0 (#6561) 2024-12-05 15:51:13 +00:00
62e1d87f5e Mention internal timings deprecations
plugins shouldn't be using these, but since it's not marked as internal, we can't be sure.
2024-12-05 15:47:34 +00:00
ea068d4907 Update 5.23.md 2024-12-05 15:01:49 +00:00
fa7bc78e7c Prepare 5.23.0 release 2024-12-05 14:29:17 +00:00
0aaf4238a8 more deprecations in line with major-next 2024-12-05 13:02:09 +00:00
35a90d24ec AsyncTask: deprecate progress update related stuff 2024-12-05 12:57:26 +00:00
9a6e258b6c Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-12-05 10:13:06 +00:00
a1448bfb88 5.22.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12160926590
2024-12-04 13:38:41 +00:00
ba6828c6bd Release 5.22.0 (Bedrock 1.21.50 support) (#6559)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-12-04 13:36:52 +00:00
5fc96c393d Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12151373979
2024-12-04 01:39:58 +00:00
2d0321ff02 Switch back to official JsonMapper
the issues that led to the need for a fork have been addressed in the 5.0.0 release.
2024-12-03 15:19:38 +00:00
c56d4d3e3c dependabot: update github actions deps together, monthly 2024-12-03 14:56:22 +00:00
06028aac97 issues: don't recommend forums to get help 2024-12-03 02:07:58 +00:00
950f7ad7a4 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12131296321
2024-12-03 01:39:32 +00:00
49da50659f Bump docker/build-push-action from 6.9.0 to 6.10.0 (#6553) 2024-12-02 16:36:12 +00:00
ae9b4dbb05 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12111121061
2024-12-02 01:41:01 +00:00
fcef015f32 L link 2024-12-02 00:40:55 +00:00
f1a3b42620 Implement frost walker enchantment (#5497)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-12-01 19:46:38 +00:00
f3763ae691 Implement Recovery compass (#5502)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-12-01 18:25:45 +00:00
12214792b3 Allow eating in creative & peaceful
closes #5923
closes #6056
2024-12-01 17:42:26 +00:00
93a9007f3c Added ClosureCommand (#6063)
this is intended to replace PluginCommand and CommandExecutor, both of which are overengineered and unfit for purpose.
Allowing a closure allows much greater flexibility.

We can't use this within the core yet, as plugins will expect PluginBase->getCommand() to return PluginCommand (with its associated setExecutor() and similar APIs).
However, I think this is useful enough to add by itself.
2024-12-01 15:10:07 +00:00
a593180ef9 Deprecate some stuff 2024-12-01 15:01:41 +00:00
61560ec375 Support for collecting timings from threads, and implement async task timings (#6333)
The following callbacks can now be registered in timings, to allow threads to be notified of these events:
- Turning on/off (`TimingsHandler::getToggleCallbacks()->add(...)`)
- Reset (`TimingsHandler::getReloadCallbacks()->add(...)`)
- Collect (`TimingsHandler::getCollectCallbacks()->add(...)`)

Collect callbacks must return `list<Promise>`. The promises must be `resolve()`d with `list<string>` of printed timings records, as returned by `TimingsHandler::printCurrentThreadRecords()`. It's recommended to use 1 promise per thread.

A timings report will be produced once all promises have been resolved.

This system is used internally to collect timings for async tasks (closes #6166).

For timings viewer developers:
Timings format version has been bumped to 3 to accommodate this change. Timings groups should now include a `ThreadId`  at the end of timings group names to ensure that their record IDs are segregated correctly, as they could otherwise conflict between threads. The main thread is not required to specify a thread ID. See pmmp/timings@13cefa6279 for implementation examples.

New PHPStan error is caused by phpstan/phpstan#10924
2024-12-01 14:49:27 +00:00
44771c892d Use PlayerAuthInputPacket::SNEAKING flag to test for sneaking (#6544)
This binds internal sneaking to whether or not the player is currently pressing the shift key, which fixes #5792 and fixes #5903.

However, it does introduce visual issues with sneaking, as explained in #6548. This needs to be worked on separately. For now, it's better we trade 2 functional bugs for 1 visual bug.
2024-12-01 13:14:30 +00:00
89f88a9333 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12092463227
2024-11-30 01:35:06 +00:00
9cab72ed59 5.21.3 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12090411351
2024-11-29 20:43:17 +00:00
2bd9f4108b 5.21.2 (#6545) 2024-11-29 20:42:14 +00:00
cd2a1fdc1d Bump build/php from 8a396c6 to 5016e0a (#6541) 2024-11-29 11:03:27 +00:00
a396233e57 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12077579940
2024-11-29 01:37:52 +00:00
d666f17ec6 Bump build/php from a51259d to 8a396c6 (#6540) 2024-11-28 15:46:30 +00:00
a84326cb2d Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12060967026
2024-11-28 01:37:49 +00:00
0543bf301e draft-release: updated php_download_url 2024-11-27 19:19:51 +00:00
603ab081bc Merge branch 'stable' into minor-next 2024-11-27 17:56:34 +00:00
daacc8eddb Updated setup-php-action to 3.2.0 2024-11-27 17:56:00 +00:00
269effcecf Introduce Utils::getRandomFloat() (#6532)
Drop-in replacement for lcg_value() for PHP 8.4
2024-11-26 11:33:29 +00:00
ae750b60d0 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12022106146
2024-11-26 01:36:41 +00:00
905a10e980 Deprecate InventoryAction->onAddToTransaction()
this never made any sense
2024-11-25 21:39:35 +00:00
52fe2cb97f PermissionManager: deprecate permission subscription system
this is no longer used by the core, and as far as I can tell no plugin uses it either.
it was used in the past for chat broadcast channels, but not anymore.
2024-11-25 21:29:26 +00:00
8cdc7d7ee1 auto-approve: drop pull_request_review trigger
this doesn't work for PRs from forks, since fork PRs don't have access to repo secrets.

we'll need some more advanced mechanism to avoid redundant reviews, but that's a job for another time.
2024-11-25 20:43:59 +00:00
12ae8dc03b Merge branch 'stable' into minor-next 2024-11-25 14:32:30 +00:00
a9787f0d99 Fix PHPStan error 2024-11-25 14:32:17 +00:00
5325ecee37 Deal with a whole lot of PHPStan suppressed key casting errors
closes #6534
2024-11-25 14:30:58 +00:00
30ee0aa63d Merge branch 'stable' into minor-next 2024-11-24 23:50:56 +00:00
aef4fa7174 Remove unused variable 2024-11-24 23:50:30 +00:00
53aa380ca3 Candle: fix extinguish logic
closes #5983
2024-11-24 23:49:32 +00:00
a5f607138c Implement Ice Bomb (#5452)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-11-24 15:01:26 +00:00
8338ebaffd Add generic types for TaskHandler (#6030) 2024-11-24 14:14:34 +00:00
082119cfd2 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990667732
2024-11-23 21:49:01 +00:00
7460e12b6a pr-remove-waiting-label: suppress failure on 404 errors
this usually means the label wasn't on the PR in the first place
2024-11-23 21:48:30 +00:00
a53b0116a0 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990103798
2024-11-23 20:14:23 +00:00
5b72f202bf actions: automatically remove waiting label from PRs on synchronize
there are probably other conditions where we'd want to remove this, but this will do for now.
2024-11-23 20:13:46 +00:00
8b6adf86d6 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11989715327
2024-11-23 19:10:12 +00:00
0070426e97 auto-approve: only re-review if previous review was dismissed
this avoids unnecessary spam when someone clicks "Update branch" on the PR.
2024-11-23 19:07:24 +00:00
a523ed6e40 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11964819241
2024-11-22 01:36:16 +00:00
7c2ed7d884 Fix insta break check (#6528) 2024-11-21 02:22:10 +00:00
1e3a858de6 Add setUp and tearDown for event unit tests 2024-11-20 16:42:50 +00:00
844ba0ff9f Move event test fixtures to a subdirectory 2024-11-20 15:40:34 +00:00
49bdaee930 Move event handler inheritance test to PHPUnit using mock objects
this is dodgy and we shouldn't rely on it long term.

relates to #6524
2024-11-20 15:38:51 +00:00
9195c88670 ConsoleReader: Use proc_open()'s socket support to send commands back to the main server process (#5273)
Support for this was introduced in PHP 8.0, though not mentioned in any changelog: php/php-src#5777

This simplifies the subprocess handling considerably. However, there is a potential for problems if PHP generates any E_* errors, since these get written to STDOUT as well.
To avoid error messages being treated as a command, a hash is attached to each IPC message, seeded with an incrementing counter. This prevents error messages causing command replays or unintended commands.

Unfortunately, PHP doesn't support binding pipes other than stdin/stdout/stderr on Windows for the child process, so we have to use stdout for this. In the future, if it becomes possible, a dedicated pipe for the purpose should be introduced. We'd need something like php://fd/<number> to work on Windows.
2024-11-20 14:56:52 +00:00
ae19d05fd5 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11924942897
2024-11-20 01:27:30 +00:00
bf7a53b00f Update api-change-request.md
[ci skip]
2024-11-19 17:47:46 +00:00
e710b3750f Adjust pretty name of closures on PHP 8.4 (#6351)
related to https://github.com/php/php-src/pull/13550

see analog symfony change: https://github.com/symfony/symfony/pull/54614
2024-11-19 17:05:21 +00:00
8ccd1edb17 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11904511045
2024-11-19 01:36:11 +00:00
faf1e26bac Fix: implicitly nullable parameter declarations deprecated (#6522) 2024-11-18 23:54:22 +00:00
b4259ef988 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11874902000
2024-11-17 01:39:52 +00:00
b2aa6396c3 auto-approve: don't request approvals for draft PRs 2024-11-16 23:15:07 +00:00
1555fa17e7 Added ability to pick end crystal item (#6509) 2024-11-16 19:06:03 +00:00
e77f2c5198 Implemented End Crystal (#4715)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-11-16 17:57:57 +00:00
e8e441f739 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11866065217
2024-11-16 01:37:27 +00:00
48a908ee8c maybe making this specific to gameplay wasn't a good idea 2024-11-16 01:36:51 +00:00
dc87a2b10e Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11865975725
2024-11-16 01:26:31 +00:00
d3add78d3e Add support for basic entity picking via middle-click (#5397)
Support for more advanced stuff like NBT copying wasn't added in this PR, as the NBT used by PM is currently an inconsistent mess and doesn't play nice with vanilla. In the interests of avoiding this mess propagating, it's been left for another time.

Adds PlayerEntityPickEvent a la PlayerBlockPickEvent
and Entity->getPickedItem().
2024-11-15 21:27:10 +00:00
ff695a5f97 PlayerInteractEvent: added APIs to independently control reaction of item and block (#4683)
This allows, for example, banning the usage of spawn eggs, without preventing opening of doors, without the need for item ID whitelists.

It also allows customizing the behaviour of item and block interactions when sneaking - it's now possible to force spawn eggs to work when sneaking, or force containers to open.

Finally, this also allows preventing any interaction at all without preventing block placement (by setting both to false). Since cancelling the event will typically prevent placement too (which might not be desired).

Side note: Blocks are now always synced when right-clicking on a block.
This differs from the previous behaviour, where the blocks were only synced when the action "failed".
However, since this change introduces a situation where the action may succeed but have different results than the client expects, it's best to just always sync blocks in this situation.

Fixes #3267
2024-11-15 21:19:54 +00:00
4b630cb726 start.sh: print warnings on unusual exit codes from the server process (#6497) 2024-11-15 21:14:21 +00:00
4331f69b9c Update team-pr-auto-approve.yml 2024-11-15 20:44:51 +00:00
fb1213e964 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11862895039
2024-11-15 20:21:32 +00:00
8a693f2a4c team-pr-auto-approve: Use RestrictedActions auto approver 2024-11-15 20:08:54 +00:00
8474eaf5f1 Adjust Sugar Cane to break when there is no water (#6486) 2024-11-15 16:27:27 +00:00
a75d4687ce Implemented vanilla /xp command (#6429) 2024-11-15 16:09:55 +00:00
0b0c425805 Extract glow lichen multi face logic into traits (#6396)
This will be useful for future block additions
2024-11-15 15:47:26 +00:00
b5469dede2 Flowable blocks now can't be placed inside liquid (#5392) 2024-11-15 00:10:43 +00:00
e0d270a870 Merge branch 'minor-next' of github.com:pmmp/PocketMine-MP into minor-next 2024-11-14 23:14:33 +00:00
2d9cee3d62 Update Language dependency 2024-11-14 23:14:23 +00:00
ed64231c57 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-11-14 23:12:26 +00:00
33a7b46329 Use reflection to locate BlockTypeIds and ItemTypeIds for VanillaBlocks/VanillaItems (#6498)
Use reflection to locate BlockTypeIds and ItemTypeIds for VanillaBlocks/VanillaItems

Since BlockTypeIds and ItemTypeIds are derived from VanillaBlocks and VanillaItems respectively anyway (they only exist to allow identifying blocks/items without having to create instances of them), this hack is probably OK, and reduces the chances of mistakes.
Previously it was explored to have these IDs generated by auto-incrementing in VanillaBlocks/Items and have the constants generated that way, but this proved to be too problematic because of unstable diffs no matter how we chose to sort the elements. See #6313 for previous research on the subject.

This is obviously not a desirable hack to keep long-term. In the future it will probably make sense to redesign VanillaBlocks like so:

enum VanillaBlocks { ... }
VanillaBlocks::STONE (the type ID)
VanillaBlocks::STONE->new() (to create a block)

However, more research is needed on this, as I'd prefer not to make block creation any more verbose.
2024-11-14 17:32:22 +00:00
9b58d35516 Implement Goat horns (#5232)
Co-authored-by: ipad54 <63200545+ipad54@users.noreply.github.com>
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-11-14 13:57:07 +00:00
3629ee7e7b Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11824307499
2024-11-13 19:21:16 +00:00
fbeb017670 Promise: allow zero promises
not supporting this has caused problems every time this function has been used in reality so far (#6092 and #6333).
2024-11-13 14:55:14 +00:00
09bf203267 Update RuntimeDataDescriber.php 2024-11-12 22:57:14 +00:00
f3cc4a28e1 Easy wins for PHPStan 2.0 support 2024-11-12 22:12:54 +00:00
fe70150db2 Update composer dependencies 2024-11-12 21:47:52 +00:00
054538e6b7 Update PULL_REQUEST_TEMPLATE.md 2024-11-12 17:54:31 +00:00
3586bc42a9 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11802296296
2024-11-12 17:14:24 +00:00
1f86949836 Create CODEOWNERS 2024-11-12 14:37:02 +00:00
053a71c59d Bump build/php from 084822a to a51259d (#6495) 2024-11-12 14:28:32 +00:00
07d5046b83 5.21.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/11796630402
2024-11-12 11:48:59 +00:00
4a702b97fd Prepare 5.21.1 release (#6493) 2024-11-12 11:48:01 +00:00
d2c3b8dacb Fix GC cycle count increases on player disconnect (#6487) 2024-11-11 16:10:19 +01:00
fa3529966f Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11769186885
2024-11-10 22:56:08 +00:00
2ff6470792 Fixed server crash when applying item cooldown (#6491)
This commit fixes server crash when applying a cooldown to any item which count is equals to 1.

closes #6490 
closes #6488
2024-11-10 19:15:30 +00:00
05a9e9c76e Implemented sound when drinking a potion (#6444) 2024-11-10 15:07:18 +00:00
231eec911f Enchanted Golden Apple: Regeneration 5 => 2 matching Java (#6445) 2024-11-09 19:43:30 +00:00
8c04d47b1b Make weakness effect only applicable for melee damage (#6489) 2024-11-09 19:18:30 +00:00
b3b8aaddff Bump docker/build-push-action from 6.8.0 to 6.9.0 (#6465) 2024-11-04 16:29:01 +00:00
2173aab967 Bump thollander/actions-comment-pull-request from 2 to 3 (#6482) 2024-11-04 16:05:38 +00:00
e598364f06 5.21.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/11652565588
2024-11-03 15:30:16 +00:00
734ca1cc6b Merge pull request #6480 from pmmp/minor-next
5.21.0
2024-11-03 15:29:06 +00:00
96b12bddc1 Prepare 5.21.0 release 2024-11-03 15:24:43 +00:00
84464cde4f Update BedrockBlockUpgradeSchema 2024-11-03 14:44:50 +00:00
72fc138631 Regenerate PHPStan baselines 2024-11-03 14:43:34 +00:00
c63d0ef1b6 Fix dodgy ignored PHPStan error 2024-11-03 14:43:34 +00:00
82c416624d Merge pull request #6416 from pmmp/blockstate-schema-generator-improvements
Blockstate schema generator improvements
2024-11-03 14:10:38 +00:00
9e19391f20 Merge branch 'minor-next' into blockstate-schema-generator-improvements 2024-11-03 14:06:57 +00:00
c0b74b0341 Update BlockStateUpgrader.php 2024-11-03 14:05:46 +00:00
3c96e72f7d Merge remote-tracking branch 'origin/stable' into minor-next 2024-11-03 14:01:47 +00:00
0376e37966 5.20.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/11614028030
2024-10-31 14:38:30 +00:00
94dff74494 Prepare 5.20.1 release (#6479) 2024-10-31 14:35:42 +00:00
0065fe649f New release workflow triggered by the merge of a PR changing
IS_DEVELOPMENT_BUILD to false

This is more streamlined than the previous approach, and works better
for a world where 1 person isn't doing all the work.

Now, the flow is simpler:
- Do changes (e.g. protocol update), changelog & set IS_DEVELOPMENT_BUILD to false all in a single PR, which can be squash-merged if desired
- Once the PR is merged, a draft release will be prepared
- RestrictedActions will automatically set IS_DEVELOPMENT_BUILD back to
  true and bump the version
- Tag will be created when the release is published

Previously, multiple PRs might be needed, and the PR containing the
release changelog couldn't be squash-merged. Manual intervention was
also required to create a tag and prepare a release.

This PR also includes new CI checks to check for basic errors like
forgotten changelog files to ensure changelog links work correctly.

Note: Only PRs from PMMP Team members with **write** access to the
repository can trigger release generation. Random people cannot trigger
release generation by sending PRs.
2024-10-31 13:57:38 +00:00
8ef5e737de Temporary resolve loading old skulls from storage (#6476) 2024-10-27 01:29:34 +01:00
e7d8d99ca6 5.20.1 is next 2024-10-26 15:42:43 +01:00
414e8acf8c Release 5.20.0 2024-10-26 15:42:43 +01:00
d372af351a Fix changelog typo 2024-10-26 15:40:22 +01:00
4814db4fe7 Assemble 1.21.40 (#6471) 2024-10-25 14:21:51 +01:00
d01203d7c4 Reduce code duplication 2024-10-24 18:50:39 +01:00
847f931660 Merge branch 'minor-next' into blockstate-schema-generator-improvements 2024-10-24 17:46:57 +01:00
22718c4971 Add support for specialized flattenedProperties in schema format 2024-10-24 16:12:28 +01:00
acbfb0a3e9 Support for updating a batch of schemas using BlockPaletteArchive 2024-10-24 13:30:55 +01:00
7f9e79c83e Automatically test new schemas to ensure they produce the results predicted by the input file 2024-10-24 13:30:24 +01:00
7e343617b9 Rename ICopper to CopperMaterial (#6470) 2024-10-23 11:34:42 +01:00
d945cbf517 Merge remote-tracking branch 'origin/minor-next' into blockstate-schema-generator-improvements 2024-10-17 21:00:11 +01:00
c4e72c880e Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11392123557
2024-10-17 19:59:13 +00:00
3ef7001d8e Merge branch 'stable' into blockstate-schema-generator-improvements 2024-10-17 20:55:34 +01:00
f1b1a7022d and a sanity check just in case 2024-10-17 20:55:12 +01:00
59d14de1d8 generate-blockstate-upgrade-schema: fallback to exact state match when encountering ambiguous filters
this popped up due to new changes in 1.20.40. Really we need to improve the way the filters are calculated, but this workaround solves the issue for now.
2024-10-17 20:51:17 +01:00
c8f567b093 Fix missing arg count check 2024-10-17 20:24:57 +01:00
5cc1068cd4 Bump docker/build-push-action from 6.7.0 to 6.8.0 (#6462) 2024-10-03 21:29:22 +00:00
f6e6f15c63 Implemented a proper way to handle items cooldown (#6405) 2024-09-25 13:28:17 -05:00
4e6b34f573 Implement new 1.21 copper blocks (#6366)
Added the following new blocks:
- All types of Copper Bulb
- All types of Copper Door
- All types of Copper Trapdoor
- All types of Chiseled Copper
- All types of Copper Grate
2024-09-24 21:25:10 -05:00
a4a07a8e5a Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10986731877
2024-09-23 02:36:00 +00:00
0a7cbdd56d Release 5.19.0 (1.21.30 support) 2024-09-21 11:50:04 -05:00
a5babb2c9f 5.19.1 is next 2024-09-20 19:00:23 -05:00
49c2f13cf0 Release 5.19.0 2024-09-20 18:59:45 -05:00
60cac18104 Assemble 1.21.30 (#6453) 2024-09-20 18:47:25 -05:00
f6e2a1ecce Validate transaction slots (#6304) 2024-09-09 09:48:38 +02:00
00e39821f0 Release 5.18.1 (patch) 2024-09-03 06:46:21 -05:00
72d941fc1b Update 5.18.md 2024-09-03 11:33:05 +01:00
8cb2e577a1 Implement missing last interacted slot property in chiseled bookshelf (#6440) 2024-09-02 18:02:06 -05:00
2a7b183ab8 5.18.2 is next 2024-09-02 11:25:50 -05:00
e9b597af6c Release 5.18.1 2024-09-02 11:24:42 -05:00
9381fc4172 Blue Ice: No longer emits light & it's now dropped when mined with a tool with silk touch enchantment (#6438) 2024-08-31 22:33:11 -06:00
281afb6838 Bump phpstan/phpstan in the development-patch-updates group (#6435) 2024-08-26 23:05:11 +00:00
ede363eb0f Fix shift crafting (#6433)
This field was added to the action in 1.21.20. Previously, the client would behave as if clicking the crafting result slot many times. Now it behaves more like recipe book shift-clicking.
2024-08-22 21:53:21 +01:00
bdbcfd10cc Add ShellCheck (#6407)
Co-authored-by: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com>
2024-08-19 22:52:51 +01:00
e6f9cdd990 Bump docker/build-push-action from 6.6.1 to 6.7.0 (#6432) 2024-08-19 18:12:06 +00:00
5241118f0b Merge branch 'minor-next' into blockstate-schema-generator-improvements 2024-08-19 19:05:08 +01:00
93a270d251 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10458521667
2024-08-19 18:03:24 +00:00
a7638cf914 Give an approving review to team member PRs automatically
Branch protection rules currently require 2 approving reviews to merge a PR. What we really want is for 2 team members to be aware of every change. If a team member makes a PR, only one other approval should be needed.

Since GitHub doesn't currently allow us to set different review thresholds for different users/teams, sending an automatic approval via GitHub Actions is the next best thing. This should reduce friction of team development work.
2024-08-19 19:02:17 +01:00
c44e6c59fc Merge pull request #6430 from pmmp/sync-minor-next
Merge branch 'stable' into minor-next
2024-08-18 17:49:25 +01:00
92f380bb9c Merge branch 'stable' into sync-minor-next 2024-08-17 11:17:48 -05:00
c32744ebc7 Release 5.18.0 (1.21.20 support) 2024-08-16 08:44:40 -05:00
e3baf3cddb 5.18.1 is next 2024-08-16 07:56:01 -05:00
9176b2494a Release 5.18.0 2024-08-16 07:54:59 -05:00
0f365886e0 Assemble 1.21.20 (#6423)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
Co-authored-by: IvanCraft623 <ivancraft623@gmail.com>
2024-08-16 11:26:49 +01:00
8c3cf7a687 Use VISIBLE_MOB_EFFECTS actor metadata property to send effect bubbles (#6414)
Close #6402
2024-08-14 22:48:10 -05:00
994fa5f792 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10373700926
2024-08-13 16:37:45 +00:00
3ed9615180 Merge pull request #6422 from pmmp/5.17.1
Release patch update 5.17.1
2024-08-13 17:28:27 +01:00
f5ab2979a0 5.17.2 is next 2024-08-13 10:56:03 -05:00
929cd63135 Release 5.17.1 2024-08-13 10:55:29 -05:00
585dc835e7 Fixed anvil placing rotation. (#6375)
Co-authored-by: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com>
2024-08-13 16:19:07 +01:00
13f5cc9f87 ChiseledBookshelf: fixed function visibility (#6421) 2024-08-13 16:11:22 +01:00
1ce774ae73 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10371827817
2024-08-13 14:36:00 +00:00
d077bda30c Bump docker/build-push-action from 6.5.0 to 6.6.1 (#6419) 2024-08-13 14:35:01 +00:00
e017780cc3 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-08-13 13:37:56 +00:00
25c66e4c8b Bump phpstan/phpstan in the development-patch-updates group (#6420) 2024-08-13 13:36:14 +00:00
ee17ac5246 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-08-09 13:32:07 +01:00
33dc995cc7 blockstate-upgrade-schema-utils: added a command to update old schemas to a newer format
this is useful when the generator was updated with new features & optimisations, to reduce the size and/or improve readability of existing schemas.
2024-08-09 13:12:46 +01:00
26761c2b87 Blockstate schema tool now supports testing schemas as well as generating them
this is useful when making changes to the generator, since regenerated schemas can now be tested for validity. This helps to find bugs in the generator.
2024-08-09 12:38:03 +01:00
5a926a79cb Bump phpstan/phpstan in the development-patch-updates group (#6412) 2024-08-06 14:21:20 +00:00
c4a2b6494d Implement Aqua Affinity enchantment (#6408) 2024-08-06 09:12:47 -05:00
2aa64dc15e Simplify phpstan-doc type hint for better readability 2024-08-05 17:13:23 -05:00
d0d7a995fb Add a TODO in BlockStateUpgrader
this issue can be worked around by adding a dummy schema, but it's a bit clunky.
2024-08-05 22:38:32 +01:00
be2437ac6e Support for flattening TAG_Byte and TAG_Int properties
this allows optimisation in upcoming versions.
2024-08-05 22:38:02 +01:00
bdb5845cec Allow name flattening rules where multiple old values map to the same new ID
this allows more compaction in certain cases, such as tallgrass recently.
instead of blacklisting any mapping which reuses the same flattened infix, we select the flatten property which produces the smallest number of distinct rules, which produces the most compact schema possible.
this change also permits potentially flattening other types of properties such as for corals (live/dead and type), although only one may be selected at a time.
2024-08-05 22:34:40 +01:00
54e7749c0b Bump docker/build-push-action from 6.3.0 to 6.5.0 (#6409) 2024-07-30 06:52:28 +00:00
237677c028 Bump phpstan/phpstan from 1.11.2 to 1.11.8 in the development-patch-updates group (#6410) 2024-07-30 06:47:56 +00:00
787afb6b00 Implement all new 1.21 tuff blocks (#6391) 2024-07-23 11:34:46 -05:00
df4ada81e5 BedrockWorldData: Update version constants to 1.21.2 (#6399)
Co-authored-by: Dylan T <14214667+dktapps@users.noreply.github.com>
2024-07-16 00:51:06 -05:00
a96f1a5083 Bump docker/build-push-action from 5.3.0 to 6.3.0 (#6387) 2024-07-14 18:17:41 +00:00
1f510caf88 Bump the phpstan group with 2 updates (#6401) 2024-07-12 13:16:44 +00:00
32be474840 dependabot: group PHPStan dependency updates always
these typically need to be updated together anyway
2024-07-12 14:12:47 +01:00
f750b01d8b dependabot: group patch updates into a single PR 2024-07-12 14:10:14 +01:00
31484ee5c1 Merge pull request #6400 from pmmp/dependabot/github_actions/shivammathur/setup-php-2.31.1 2024-07-12 12:46:19 +00:00
786d84a9e1 Bump shivammathur/setup-php from 2.30.4 to 2.31.1
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.30.4 to 2.31.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.30.4...2.31.1)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 12:43:18 +00:00
d6c48fd3a2 Implement new 1.21 paintings (#6393) 2024-07-12 13:24:43 +01:00
85606925a1 BlockStateData: add a note about CURRENT_VERSION
this is not the same as current game version, as the revision is determined by some Mojang internal factors.
2024-07-12 11:26:51 +01:00
ef9791eaed Merge pull request #6397 from pmmp/stable
Merge branch 'stable' into minor-next
2024-07-10 18:56:52 +01:00
accaa0acce Implement ICopper interface for blocks with common properties (#6390) 2024-07-10 11:15:06 -05:00
616f96a703 Release 5.17.0 (1.21.2 support) 2024-07-10 09:31:25 -05:00
824e270041 5.17.1 is next 2024-07-09 21:16:13 -05:00
2ffc38c835 Implement campfire & soul campfire (#4696) 2024-07-07 15:01:34 -05:00
f6c0b228ec Add flow and guster banner patterns 2024-07-04 16:47:18 -05:00
b342c497d1 Added 1.21 banner patterns. 2024-06-23 13:27:52 +03:00
428e38f913 Merge branch 'stable' into minor-next 2024-06-21 10:42:32 -05:00
5ae13209c0 Merge branch 'stable' into merge-stable 2024-06-20 19:21:33 -05:00
Max
371eccd007 Make access modifier consistent with parent abstract class (#6341) 2024-05-07 12:02:50 +01:00
282 changed files with 10336 additions and 4864 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @pmmp/server-developers

View File

@ -7,13 +7,13 @@ assignees: ''
---
<!--- tell us what you want -->
## Description
<!--- Describe the problem you want to solve -->
## Problem description
<!--- explain why you want this and why it's a good idea -->
## Justification
<!--- Describe what changes you want to make to solve this problem -->
## Proposed solution
<!--- (optional) describe alternative methods you've explored to achieve your goal -->
## Alternative methods
## Alternative solutions that don't require API changes

View File

@ -3,9 +3,6 @@ contact_links:
- name: Help & support on Discord
url: https://discord.gg/bmSAZBG
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
- name: Help & support on forums
url: https://forums.pmmp.io
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
- name: Documentation
url: https://pmmp.rtfd.io
about: PocketMine-MP documentation

View File

@ -1,43 +1,32 @@
## Introduction
<!-- Summarize your PR here. Keep it short and simple. -->
<!-- Explain existing problems or why this pull request is necessary -->
### Relevant issues
<!-- List relevant issues here -->
### Related issues & PRs
<!--
* Fixes #1
* Fixes #2
* Related to #2
-->
## Changes
### API changes
<!-- Any additions to the API that should be documented in release notes? -->
<!-- If not, you can delete this section -->
### Behavioural changes
<!-- Any change in how the server behaves, or its performance? -->
<!-- If not, you can delete this section -->
## Backwards compatibility
<!-- Any possible backwards incompatible changes? How are they solved, or how can they be solved? -->
<!-- If not, you can delete this section -->
## Follow-up
<!-- Suggest any actions to be done before/after merging this pull request -->
<!--
Requires translations:
| Name | Value in eng.ini |
| :--: | :---: |
| `foo.bar` | `Foo bar` |
-->
<!-- For example, future changes that this PR lays the groundwork for -->
## Tests
<!--
PRs which have not been tested MUST be marked as draft.
If this PR affects gameplay or user experience in some way, it must be manually tested.
Include any screenshots or videos of manual testing here.
Any test plugin code should also be pasted here if it can't be adapted to a PHPUnit test.
-->
I tested this PR by doing the following (tick all that apply):
- [ ] Writing PHPUnit tests (commit these in the `tests/phpunit` folder)
- [ ] Playtesting using a Minecraft client (provide screenshots or a video)
- [ ] Writing a test plugin (provide the code and sample output)
- [ ] Other (provide details)

View File

@ -12,6 +12,22 @@ updates:
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
groups:
production-patch-updates:
dependency-type: production
patterns:
- "*"
update-types:
- "patch"
development-patch-updates:
dependency-type: development
patterns:
- "*"
update-types:
- "patch"
phpstan:
patterns:
- "phpstan/*"
- package-ecosystem: gitsubmodule
directory: "/"
@ -21,4 +37,7 @@ updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
interval: monthly
groups:
github-actions:
patterns: ["*"]

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -66,7 +66,7 @@ jobs:
- name: Build image for major tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -79,7 +79,7 @@ jobs:
- name: Build image for minor tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -92,7 +92,7 @@ jobs:
- name: Build image for latest tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.30.4
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2

View File

@ -0,0 +1,111 @@
name: Release PR checks
on:
#do checks on every PR update
pull_request:
branches:
- stable
- minor-next
- major-next
- "legacy/*"
paths:
- "src/VersionInfo.php"
#allow this workflow to be invoked on PR merge, prior to creating the release
workflow_call:
outputs:
valid:
description: Whether this commit is valid for release
value: ${{ jobs.check-intent.outputs.valid && jobs.check-validity.result == 'success' }}
permissions:
contents: read #for user access check
jobs:
check-intent:
name: Check release trigger
runs-on: ubuntu-20.04
outputs:
valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }}
steps:
- uses: actions/checkout@v4
- name: Check IS_DEVELOPMENT_BUILD flag
id: validate
run: |
echo DEV_BUILD=$(sed -n "s/^\s*public const IS_DEVELOPMENT_BUILD = \(true\|false\);$/\1/p" src/VersionInfo.php) >> $GITHUB_OUTPUT
check-validity:
name: Validate release info
needs: [check-intent]
#don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses
if: needs.check-intent.outputs.valid == 'true'
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2
- name: Restore Composer package cache
uses: actions/cache@v4
with:
path: |
~/.cache/composer/files
~/.cache/composer/vcs
key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}"
restore-keys: |
composer-v2-cache-
- name: Install Composer dependencies
run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
- name: Check author permissions
id: check-permission
uses: actions-cool/check-user-permission@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
require: write
username: ${{ github.event.pull_request.user.login }}
#technically this would be fine for dependabot but generally bots don't count as team members
check-bot: true
- name: Abort if user permissions are insufficient
#user doesn't have permission or is a bot
if: steps.check-permission.outputs.require-result != 'true' || steps.check-permission.outputs.check-result != 'false'
run: |
echo "::error::This user is not authorized to trigger releases"
exit 1
- name: Check changelog file is present
id: file-presence
run: |
CHANGELOG_FILE="changelogs/$(php build/dump-version-info.php changelog_file_name)"
if [ ! -f "${{ github.workspace }}/$CHANGELOG_FILE" ]; then
echo "::error::$CHANGELOG_FILE does not exist"
exit 1
fi
echo FILE="$CHANGELOG_FILE" >> $GITHUB_OUTPUT
- name: Check header is present in changelog file
run: |
FILE="${{ steps.file-presence.outputs.FILE }}"
VERSION="$(php build/dump-version-info.php base_version)"
if ! grep -Fqx "# $VERSION" "${{ github.workspace }}/$FILE"; then
echo "::error::Header for $VERSION not found in $FILE"
exit 1
fi
- name: Check version is valid for the selected channel
run: |
CHANNEL="$(php build/dump-version-info.php channel)"
if [ "$(php build/dump-version-info.php suffix_valid)" != "true" ]; then
echo "::error::Version $(php build/dump-version-info.php base_version) is not allowed on the $CHANNEL channel"
exit 1
fi

View File

@ -1,18 +1,85 @@
name: Draft release
on:
#presume that pull_request_target is safe at this point, since the PR was approved and merged
#we need write access to prepare the release & create comments
pull_request_target:
types:
- closed
branches:
- stable
- minor-next
- major-next
- "legacy/*"
paths:
- "src/VersionInfo.php"
push:
tags: "*"
tags:
- "*"
env:
PHP_VERSION: "8.2"
jobs:
skip:
name: Check whether to ignore this tag
runs-on: ubuntu-20.04
outputs:
skip: ${{ steps.exists.outputs.exists == 'true' }}
steps:
- name: Check if release already exists
id: exists
env:
GH_TOKEN: ${{ github.token }}
run: |
exists=false
if [[ "${{ github.ref_type }}" == "tag" ]]; then
tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)"
if gh release view "$tag" --repo "${{ github.repository }}"; then
exists=true
fi
fi
echo exists=$exists >> $GITHUB_OUTPUT
check:
needs: [skip]
if: needs.skip.outputs.skip != 'true'
name: Check release
uses: ./.github/workflows/draft-release-pr-check.yml
trigger-post-release-workflow:
name: Trigger post-release RestrictedActions workflow
needs: [check]
if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag
runs-on: ubuntu-20.04
steps:
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
owner: ${{ github.repository_owner }}
repositories: RestrictedActions
- name: Dispatch post-release restricted action
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ steps.generate-token.outputs.token }}
repository: ${{ github.repository_owner }}/RestrictedActions
event-type: pocketmine_mp_post_release
client-payload: '{"branch": "${{ github.ref }}"}'
draft:
name: Create GitHub draft release
if: "startsWith(github.event.head_commit.message, 'Release ')"
needs: [check]
if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
php-version: [8.2]
steps:
- uses: actions/checkout@v4
@ -20,9 +87,9 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.30.4
uses: shivammathur/setup-php@2.31.1
with:
php-version: ${{ matrix.php-version }}
php-version: ${{ env.PHP_VERSION }}
- name: Restore Composer package cache
uses: actions/cache@v4
@ -40,7 +107,7 @@ jobs:
- name: Calculate build number
id: build-number
run: |
BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins
BUILD_NUMBER=$((2300+$GITHUB_RUN_NUMBER)) #to stay above jenkins
echo "Build number: $BUILD_NUMBER"
echo BUILD_NUMBER=$BUILD_NUMBER >> $GITHUB_OUTPUT
@ -53,23 +120,31 @@ jobs:
- name: Get PocketMine-MP release version
id: get-pm-version
run: |
echo PM_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BASE_VERSION;') >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK;') >> $GITHUB_OUTPUT
echo PM_VERSION_SHORT=$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\VersionInfo::BASE_VERSION); array_pop($v); echo implode(".", $v);') >> $GITHUB_OUTPUT
echo PM_VERSION_MD=$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);') >> $GITHUB_OUTPUT
echo CHANGELOG_SUFFIX=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "" : "-" . \pocketmine\VersionInfo::BUILD_CHANNEL;') >> $GITHUB_OUTPUT
echo PRERELEASE=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "false" : "true";') >> $GITHUB_OUTPUT
PM_VERSION=$(php build/dump-version-info.php base_version)
echo PM_VERSION=$PM_VERSION >> $GITHUB_OUTPUT
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
echo PRERELEASE=$(php build/dump-version-info.php prerelease) >> $GITHUB_OUTPUT
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)"
else
tag="$PM_VERSION"
fi
echo TAG_NAME=$tag >> $GITHUB_OUTPUT
- name: Generate PHP binary download URL
id: php-binary-url
run: |
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ env.PHP_VERSION }}-latest" >> $GITHUB_OUTPUT
- name: Generate build info
run: |
php build/generate-build-info-json.php \
${{ github.sha }} \
${{ steps.get-pm-version.outputs.PM_VERSION }} \
${{ steps.get-pm-version.outputs.TAG_NAME }} \
${{ github.repository }} \
${{ steps.build-number.outputs.BUILD_NUMBER }} \
${{ github.run_id }} \
@ -91,17 +166,24 @@ jobs:
- name: Create draft release
uses: ncipollo/release-action@v1.14.0
id: create-draft
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
commit: ${{ github.sha }}
draft: true
prerelease: ${{ steps.get-pm-version.outputs.PRERELEASE }}
name: PocketMine-MP ${{ steps.get-pm-version.outputs.PM_VERSION }}
tag: ${{ steps.get-pm-version.outputs.PM_VERSION }}
tag: ${{ steps.get-pm-version.outputs.TAG_NAME }}
token: ${{ secrets.GITHUB_TOKEN }}
body: |
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.CHANGELOG_FILE_NAME }}#${{ steps.get-pm-version.outputs.CHANGELOG_MD_HEADER }}) for details.
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
- name: Post draft release URL on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v3
with:
message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -96,7 +96,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -128,7 +128,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"

View File

@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.30.4
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2
tools: php-cs-fixer:3.49
@ -37,3 +37,15 @@ jobs:
- name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff --ansi
shellcheck:
name: ShellCheck
runs-on: ubuntu-20.04
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@2.0.0

View File

@ -0,0 +1,33 @@
name: Remove waiting label from PRs
on:
pull_request_target:
types: synchronize
jobs:
delabel:
name: Remove label
runs-on: ubuntu-latest
steps:
- name: Remove label
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const [owner, repo] = context.payload.repository.full_name.split('/');
try {
await github.rest.issues.removeLabel({
owner: owner,
repo: repo,
issue_number: context.payload.number,
name: "Status: Waiting on Author",
});
} catch (error) {
if (error.status === 404) {
//probably label wasn't set on the issue
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
} else {
throw error;
}
}

29
.github/workflows/pr-stale.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: 'Clean up stale PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: -1
days-before-issue-close: -1
stale-pr-message: |
This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days.
If there is no further activity, it will be closed in 28 days.
Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale.
close-pr-message: |
As this PR hasn't been updated for a while, unfortunately we'll have to close it.
days-before-pr-stale: 7
days-before-pr-close: 28
only-labels: "Status: Waiting on Author"
close-pr-label: "Resolution: Abandoned"
exempt-all-assignees: true

View File

@ -0,0 +1,38 @@
#Due to GitHub awkwardness, it's not easy to reduce the review requirement for collaborators.
#Our policy is that 2 collaborators should be aware of every change.
#For outside PRs, this means 2 collaborator reviews.
#For PRs made by collaborators, this means 1 reviewer + the author.
#We trust that collaborators don't need as much oversight.
name: Auto approve collaborator PRs
on:
pull_request_target:
types:
- opened
- reopened
- ready_for_review
- synchronize
jobs:
dispatch:
name: Request approval
runs-on: ubuntu-latest
if: '! github.event.pull_request.draft'
steps:
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
owner: ${{ github.repository_owner }}
repositories: RestrictedActions
- name: Dispatch restricted action
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ steps.generate-token.outputs.token }}
repository: ${{ github.repository_owner }}/RestrictedActions
event-type: auto_approve_collaborator_pr
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "0" }'

View File

@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t
- Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones.
- **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes.
- This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers.
- Check out the [issues page]() for something that you could tackle without too much effort.
- Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort.
- **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail.
- **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE.
- **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode.

View File

@ -0,0 +1,87 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\VersionInfo;
require dirname(__DIR__) . '/vendor/autoload.php';
/*
* Dumps version info in a machine-readable format for use in GitHub Actions workflows
*/
/**
* @var string[]|\Closure[] $options
* @phpstan-var array<string, string|\Closure() : string> $options
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{
$version = VersionInfo::VERSION();
$result = $version->getMajor() . "." . $version->getMinor();
$suffix = $version->getSuffix();
if($suffix !== ""){
if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){
fwrite(STDERR, "error: invalid current version suffix \"$suffix\"; aborting" . PHP_EOL);
exit(1);
}
$baseSuffix = $matches[1];
$result .= "-" . strtolower($baseSuffix);
}
return $result . ".md";
},
"changelog_md_header" => fn() : string => str_replace(".", "", VersionInfo::BASE_VERSION),
"prerelease" => fn() : bool => VersionInfo::VERSION()->getSuffix() !== "",
"channel" => VersionInfo::BUILD_CHANNEL,
"suffix_valid" => function() : bool{
//TODO: maybe this should be put into its own script?
$suffix = VersionInfo::VERSION()->getSuffix();
if(VersionInfo::BUILD_CHANNEL === "stable"){
//stable builds may not have suffixes
return $suffix === "";
}
if(VersionInfo::BUILD_CHANNEL === "alpha" || VersionInfo::BUILD_CHANNEL === "beta"){
$upperChannel = strtoupper(VersionInfo::BUILD_CHANNEL);
$upperSuffix = strtoupper($suffix);
return str_starts_with($upperSuffix, $upperChannel) && is_numeric(substr($upperSuffix, strlen($upperChannel)));
}
return true;
}
];
if(count($argv) !== 2 || !isset($options[$argv[1]])){
fwrite(STDERR, "Please provide an option (one of: " . implode(", ", array_keys($options)) . PHP_EOL);
exit(1);
}
$result = $options[$argv[1]];
if($result instanceof Closure){
$result = $result();
}
if(is_bool($result)){
echo $result ? "true" : "false";
}else{
echo $result;
}

View File

@ -122,7 +122,7 @@ if(!is_array($ids)){
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
}
$cleanedIds = [];
foreach($ids as $name => $id){
foreach(Utils::promoteKeys($ids) as $name => $id){
if(!is_string($name) || !is_int($id)){
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
}

View File

@ -42,7 +42,7 @@ $constants = [];
* @phpstan-param-out array<string, string> $constants
*/
function collectProperties(string $prefix, array $properties, array &$constants) : void{
foreach($properties as $propertyName => $property){
foreach(Utils::promoteKeys($properties) as $propertyName => $property){
$fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
$constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\update_registry_annotations;
use pocketmine\utils\Utils;
use function basename;
use function class_exists;
use function count;
@ -48,6 +49,7 @@ if(count($argv) !== 2){
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
$selfName = basename(__FILE__);
@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
static $lineTmpl = " * @method static %2\$s %s()";
$memberLines = [];
foreach($members as $name => $member){
foreach(Utils::stringifyKeys($members) as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false && $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();

View File

@ -1,164 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\build\make_release;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use pocketmine\VersionInfo;
use function array_keys;
use function array_map;
use function dirname;
use function fgets;
use function file_put_contents;
use function fwrite;
use function getopt;
use function is_string;
use function max;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_pad;
use function strlen;
use function strtolower;
use function system;
use const STDERR;
use const STDIN;
use const STDOUT;
use const STR_PAD_LEFT;
require_once dirname(__DIR__) . '/vendor/autoload.php';
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev, string $channel) : void{
$versionInfo = Filesystem::fileGetContents($versionInfoPath);
$versionInfo = preg_replace(
$pattern = '/^([\t ]*public )?const BASE_VERSION = "(\d+)\.(\d+)\.(\d+)(?:-(.*))?";$/m',
'$1const BASE_VERSION = "' . $newVersion . '";',
$versionInfo
);
$versionInfo = preg_replace(
'/^([\t ]*public )?const IS_DEVELOPMENT_BUILD = (?:true|false);$/m',
'$1const IS_DEVELOPMENT_BUILD = ' . ($isDev ? 'true' : 'false') . ';',
$versionInfo
);
$versionInfo = preg_replace(
'/^([\t ]*public )?const BUILD_CHANNEL = ".*";$/m',
'$1const BUILD_CHANNEL = "' . $channel . '";',
$versionInfo
);
file_put_contents($versionInfoPath, $versionInfo);
}
const ACCEPTED_OPTS = [
"current" => "Version to insert and tag",
"next" => "Version to put in the file after tagging",
"channel" => "Release channel to post this build into"
];
function systemWrapper(string $command, string $errorMessage) : void{
system($command, $result);
if($result !== 0){
echo "error: $errorMessage; aborting\n";
exit(1);
}
}
function main() : void{
$filteredOpts = [];
foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
$maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS)));
foreach(ACCEPTED_OPTS as $acceptedName => $description){
fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n");
}
exit(0);
}
if(!is_string($optValue)){
fwrite(STDERR, "--$optName expects exactly 1 value\n");
exit(1);
}
$filteredOpts[$optName] = $optValue;
}
$channel = $filteredOpts["channel"] ?? null;
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
$nextVer = isset($filteredOpts["next"]) ? new VersionString($filteredOpts["next"]) : null;
$suffix = $currentVer->getSuffix();
if($suffix !== ""){
if($channel === "stable"){
fwrite(STDERR, "error: cannot release a suffixed build into the stable channel\n");
exit(1);
}
if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){
echo "error: invalid current version suffix \"$suffix\"; aborting\n";
exit(1);
}
$nextVer ??= new VersionString(sprintf(
"%u.%u.%u-%s%u",
$currentVer->getMajor(),
$currentVer->getMinor(),
$currentVer->getPatch(),
$matches[1],
((int) $matches[2]) + 1
));
$channel ??= strtolower($matches[1]);
}else{
$nextVer ??= new VersionString(sprintf(
"%u.%u.%u",
$currentVer->getMajor(),
$currentVer->getMinor(),
$currentVer->getPatch() + 1
));
$channel ??= "stable";
}
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
echo "please add appropriate notes to the changelog and press enter...";
fgets(STDIN);
systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes");
system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result);
if($result === 0){
echo "error: no changelog changes detected; aborting\n";
exit(1);
}
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit");
systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag");
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit");
systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit");
}
main();

View File

@ -25,6 +25,7 @@ namespace pocketmine\server_phar_stub;
use function clearstatcache;
use function copy;
use function define;
use function fclose;
use function fflush;
use function flock;
@ -165,4 +166,5 @@ $start = hrtime(true);
$cacheName = preparePharCache($tmpDir, __FILE__);
echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n";
define('pocketmine\ORIGINAL_PHAR_PATH', __FILE__);
require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php';

View File

@ -23,3 +23,16 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if
## Fixes
- Bowl can now be used as fuel.
- Bells always drops themselves even when using an incompatible tool.
# 5.17.1
Released 13th August 2024.
## Documentation
- Added a note about `BlockStateData::CURRENT_VERSION`.
## Fixes
- Fixed anvil placement rotation to match vanilla.
- Fixed outdated `BedrockWorldData` version, this was preventing use newer worlds.
## Internals
- Dependabot: PHPStan and patch updates are now grouped into a single PR.

30
changelogs/5.18.md Normal file
View File

@ -0,0 +1,30 @@
# 5.18.0
Released 16th August 2024.
**For Minecraft: Bedrock Edition 1.21.20**
This is a support release for Minecraft: Bedrock Edition 1.21.20.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.20.
- Removed support for earlier versions.
## Fixes
- Use `VISIBLE_MOB_EFFECTS` actor metadata property to send effect bubbles, this fixes effect bubbles not showing
# 5.18.1
Released 3rd September 2024.
## Fixes
- Fixed shift-crafting.
- Blue Ice block no longer emits light & it's now dropped when mined with a tool with silk touch enchantment.
## Internals
- Pull Requests from team members now get an approval automatically. This means that if a team member makes a PR, only one other approval should be needed.
- Added [ShellCheck](https://github.com/koalaman/shellcheck) to the CI tests.

16
changelogs/5.19.md Normal file
View File

@ -0,0 +1,16 @@
# 5.19.0
Released 21st September 2024.
**For Minecraft: Bedrock Edition 1.21.30**
This is a support release for Minecraft: Bedrock Edition 1.21.30.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.30.
- Removed support for earlier versions.

25
changelogs/5.20.md Normal file
View File

@ -0,0 +1,25 @@
# 5.20.0
Released 26th October 2024.
**For Minecraft: Bedrock Edition 1.21.40**
This is a support release for Minecraft: Bedrock Edition 1.21.40.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.40.
- Removed support for earlier versions.
## Fixes
- Fixed a bug in `tools/generate-blockstate-upgrade-schema.php` that caused it to fail on 1.21.40 with the new mushroom block changes.
# 5.20.1
Released 31st October 2024.
## Fixes
- Workaround old mob heads in world saves not being upgraded correctly and causing crashes.

128
changelogs/5.21.md Normal file
View File

@ -0,0 +1,128 @@
# 5.21.0
Released 3rd November 2024.
This is a minor feature release, including gameplay features and minor internals improvements.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## Gameplay
- Added the following new blocks:
- Campfire
- Chiseled Copper
- Chiseled Tuff
- Chiseled Tuff Bricks
- Copper Bulb
- Copper Door
- Copper Grate
- Copper Trapdoor
- Polished Tuff, Slabs, Stairs and Walls
- Soul Campfire
- Tuff Bricks, Slabs, Stairs and Walls
- Tuff Slab, Stairs and Walls
- Added the following new types of painting:
- backyard
- baroque
- bouquet
- cavebird
- changing
- cotan
- endboss
- fern
- finding
- humble
- lowmist
- meditative
- orb
- owlemons
- passage
- pond
- prairie_ride
- sunflowers
- tides
- unpacked
- Armor slots are now properly restricted (on the server side) to only contain the appropriate type of armor or headwear.
- Implemented Aqua Affinity enchantment. Since the server doesn't currently enforce any movement restrictions in water, this enchantment works based on client-side behaviour only.
## API
### `pocketmine\block`
- The following new API methods have been added:
- `public ChiseledBookshelf->getLastInteractedSlot() : ?ChiseledBookshelfSlot`
- `public ChiseledBookshelf->setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : $this`
- The following new classes have been added:
- `utils\CopperMaterial` - interface implemented by all copper-like blocks with oxidation and waxed properties
- `CopperBulb`
- `CopperDoor`
- `CopperGrate`
- `CopperTrapdoor`
- `SoulCampfire`
- `Campfire`
- The following enums have new cases:
- `utils\BannerPatternType` has new cases `FLOW` and `GUSTER`
### `pocketmine\crafting`
- The following enums have new cases:
- `FurnaceType` has new cases `CAMPFIRE` and `SOUL_CAMPFIRE`
### `pocketmine\event`
- The following new classes have been added:
- `block\CampfireCookEvent` - called when a campfire finishes cooking an item
### `pocketmine\inventory`
- Added support for slot validators, which permit restricting the types of items a player can put into an inventory slot.
- The following new classes have been added:
- `transaction\action\SlotValidator` - interface
- `transaction\action\CallbackSlotValidator` - class allowing a closure to be used for slot content validation
- `SlotValidatedInventory` - implemented by inventories which support the use of slot validators
### `pocketmine\item`
- The following new API methods have been added:
- `public Item->getCooldownTag() : ?string` - returns the cooldown group this item belongs to, used for ensuring that, for example, different types of goat horns all respect a general horn cooldown
- The following new classes have been added:
- `ItemCooldownTags` - list of cooldown group tags used by PocketMine-MP
### `pocketmine\world\sound`
- The following new classes have been added
- `CampfireSound` - sound made by campfires while lit
## Tools
- `tools/blockstate-upgrade-schema-utils.php` (formerly `generate-blockstate-upgrade-schema.php`) has several improvements:
- Support for generating `flattenedProperties` rules as per [BedrockBlockUpgradeSchema 5.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/5.0.0)
- Improved criteria for flattened property selection to minimize the amount of rules required
- Several subcommands are now available:
- `generate` - generates a schema from provided data
- `update` - regenerates an existing schema in a newer format
- `update-all` - regenerates a folder of existing schemas in a newer format (useful for updating `BedrockBlockUpgradeSchema` en masse)
- `test` - verifies that a schema produces the results expected by provided data
## Internals
- Fixed incorrect visibility of `createEntity` in spawn eggs.
- Added support for newer `BedrockBlockUpgradeSchema` in `BlockStateUpgrader`.
# 5.21.1
Released 12th November 2024.
## Fixes
- Fixed server crash when applying a cooldown to an item with 1 count.
- Fixed garbage collector cycle count increase on player disconnect.
- Fixed weakness effect being applied to all attack types, causing damage splash potions to become weaker.
- Fixed Enchanted Golden Apple regeneration effect amplifier to match vanilla.
# 5.21.2
Released 29th November 2024.
## Fixes
- Fixed blocks destroyable by water being able to be placed inside water.
- Fixed deprecation warnings about nullable typehints on PHP 8.4.
- Fixed `Utils::getNiceClosureName()` not working correctly on PHP 8.4.
- Fixed incorrect break animations when breaking the block behind an instantly-breakable block like a torch.
- Fixed candle extinguish logic.
- Fixed various documentation issues around array key types.
- Introduced a new PHPStan rule along with `Utils::promoteKeys()` to improve PHPStan error reporting around unspecified array key types. Previously, some errors were missed due to PHPStan's BenevolentUnionType.
## DevOps
- `pmmp/server-developers` team is now automatically requested for review on any new PR.
- `Status: Waiting on Author` label is now automatically removed from PRs when they are updated.

16
changelogs/5.22.md Normal file
View File

@ -0,0 +1,16 @@
# 5.22.0
Released 4th December 2024.
**For Minecraft: Bedrock Edition 1.21.50**
This is a support release for Minecraft: Bedrock Edition 1.21.50.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.50.
- Removed support for earlier versions.

129
changelogs/5.23.md Normal file
View File

@ -0,0 +1,129 @@
# 5.23.0
Released 5th December 2024.
This is a minor feature release, including new gameplay features, internals improvements, API additions and
deprecations, and improvements to timings.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load.
- Added `/xp` command.
- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors.
## Gameplay
- Added the following new items:
- End Crystal
- Goat Horn (all variants)
- Ice Bomb (from Education Edition)
- Recovery Compass
- Added the following enchantments:
- Frost Walker
- Sugarcane now self-destructs when there is no water adjacent to the base block.
- Added basic support for middle-clicking on entities to get their spawn eggs.
- Added sounds when drinking potions.
- Eating food is now allowed in creative mode and in peaceful difficulty.
## API
### `pocketmine\block`
- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks.
- The following API methods have been deprecated:
- `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work
### `pocketmine\command`
- The following classes have been added:
- `ClosureCommand` - allows registering a closure to execute a command
### `pocketmine\event`
- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions.
- This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc.
- If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed).
- The following API methods have been added:
- `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void`
- `public PlayerInteractEvent->setUseItem(bool $useItem) : void`
- `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc).
- `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc).
- The following new classes have been added:
- `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity
### `pocketmine\inventory\transaction`
- The following API methods have been deprecated:
- `InventoryAction->onAddToTransaction()`
### `pocketmine\permission`
- The following API methods have been deprecated:
- `PermissionManager->getPermissionSubscriptions()`
- `PermissionManager->subscribeToPermission()`
- `PermissionManager->unsubscribeFromAllPermissions()`
- `PermissionManager->unsubscribeFromPermission()`
### `pocketmine\plugin`
- The following classes have been deprecated:
- `DiskResourceProvider`
- `ResourceProvider`
### `pocketmine\promise`
- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array.
### `pocketmine\scheduler`
- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`.
- The following API methods have been deprecated
- `AsyncTask->publishProgress()`
- `AsyncTask->onProgressUpdate()`
### `pocketmine\timings`
- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected.
- The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report.
- Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings.
- PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future.
- The following API methods have been added:
- `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list<Promise<list<string>>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`.
- `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset
- `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled
- `public static TimingsHandler::requestPrintTimings() : Promise<list<string>>` - asynchronously collects timing results from all threads and assembles them into a single report
- The following API methods have been deprecated:
- `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead.
- `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed
- The following constants have been deprecated:
- `Timings::GROUP_BREAKDOWN` - no longer used
### `pocketmine\utils`
- The following API methods have been added:
- `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4.
## Internals
- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins.
- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified.
- While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively.
- It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`.
- In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely.
- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions.
- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
- Fixed various deprecation warnings in PHP 8.4.
- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.
# 5.23.1
Released 5th December 2024.
## Fixes
- Fixed signs not creating a tile when placed.
## Internals
- Improved blockstate consistency check to detect tiles disappearing during refactors.
# 5.23.2
Released 9th December 2024.
## General
- Updated translations for Russian and Korean.
## Fixes
- Fixed server build number.
- Fixed some crashes being misreported as plugin-involved.
## Internals
- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow.

View File

@ -32,16 +32,16 @@
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0",
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
"pocketmine/bedrock-block-upgrade-schema": "~4.2.0+bedrock-1.21.0",
"pocketmine/bedrock-data": "~2.11.0+bedrock-1.21.0",
"pocketmine/bedrock-item-upgrade-schema": "~1.10.0+bedrock-1.21.0",
"pocketmine/bedrock-protocol": "~32.1.0+bedrock-1.21.2",
"netresearch/jsonmapper": "~v5.0.0",
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.19.0",
"pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
@ -52,7 +52,7 @@
"symfony/filesystem": "~6.4.0"
},
"require-dev": {
"phpstan/phpstan": "1.11.2",
"phpstan/phpstan": "1.11.11",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^10.5.24"

373
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d459605b85f247204988a879982aab50",
"content-hash": "732102eca72dc1d29e7b67dfbce07653",
"packages": [
{
"name": "adhocore/json-comment",
@ -126,17 +126,68 @@
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "4.2.0",
"name": "netresearch/jsonmapper",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "8a327197b3b462fa282f40f76b070ffe585a25d2"
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/8a327197b3b462fa282f40f76b070ffe585a25d2",
"reference": "8a327197b3b462fa282f40f76b070ffe585a25d2",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Map nested JSON structures onto PHP classes",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0"
},
"time": "2024-09-08T10:20:00+00:00"
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
"shasum": ""
},
"type": "library",
@ -147,22 +198,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.2.0"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
},
"time": "2024-06-13T17:28:26+00:00"
"time": "2024-11-03T14:13:50+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "2.11.0+bedrock-1.21.0",
"version": "2.15.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "cae40bde98081b388c4d3ab59d45b8d1cf5d8761"
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/cae40bde98081b388c4d3ab59d45b8d1cf5d8761",
"reference": "cae40bde98081b388c4d3ab59d45b8d1cf5d8761",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"shasum": ""
},
"type": "library",
@ -173,22 +224,22 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.0"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50"
},
"time": "2024-06-13T17:17:55+00:00"
"time": "2024-12-04T12:59:12+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.10.0",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "b4687afa19f91eacebd46c40d487f4cc515be504"
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/b4687afa19f91eacebd46c40d487f4cc515be504",
"reference": "b4687afa19f91eacebd46c40d487f4cc515be504",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"shasum": ""
},
"type": "library",
@ -199,22 +250,22 @@
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.10.0"
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0"
},
"time": "2024-05-15T15:15:55+00:00"
"time": "2024-12-04T12:22:49+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "32.1.0+bedrock-1.21.2",
"version": "35.0.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "bb23db51365bdc91d3135c3885986a691ae1cb44"
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bb23db51365bdc91d3135c3885986a691ae1cb44",
"reference": "bb23db51365bdc91d3135c3885986a691ae1cb44",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"shasum": ""
},
"require": {
@ -227,7 +278,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.11.2",
"phpstan/phpstan": "1.11.9",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5 || ^10.0"
@ -245,9 +296,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/32.1.0+bedrock-1.21.2"
"source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50"
},
"time": "2024-07-10T01:38:43+00:00"
"time": "2024-12-04T13:02:00+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -420,16 +471,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.19.6",
"version": "2.22.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e"
"reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
"url": "https://api.github.com/repos/pmmp/Language/zipball/fa4e377c437391cfcfdedd08eea3a848eabd1b49",
"reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49",
"shasum": ""
},
"type": "library",
@ -437,9 +488,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.19.6"
"source": "https://github.com/pmmp/Language/tree/2.22.1"
},
"time": "2023-08-08T16:53:23+00:00"
"time": "2024-12-06T14:44:17+00:00"
},
{
"name": "pocketmine/log",
@ -565,60 +616,6 @@
},
"time": "2023-07-14T13:01:49+00:00"
},
{
"name": "pocketmine/netresearch-jsonmapper",
"version": "v4.4.999",
"source": {
"type": "git",
"url": "https://github.com/pmmp/netresearch-jsonmapper.git",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"replace": {
"netresearch/jsonmapper": "~4.2.0"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999"
},
"time": "2024-02-23T13:17:01+00:00"
},
{
"name": "pocketmine/raklib",
"version": "1.1.1",
@ -926,16 +923,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.4.9",
"version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "b51ef8059159330b74a4d52f68e671033c0fe463"
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463",
"reference": "b51ef8059159330b74a4d52f68e671033c0fe463",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3",
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3",
"shasum": ""
},
"require": {
@ -972,7 +969,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.9"
"source": "https://github.com/symfony/filesystem/tree/v6.4.13"
},
"funding": [
{
@ -988,24 +985,24 @@
"type": "tidelift"
}
],
"time": "2024-06-28T09:49:33+00:00"
"time": "2024-10-25T15:07:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -1051,7 +1048,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@ -1067,24 +1064,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -1131,7 +1128,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -1147,22 +1144,22 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.12.0",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@ -1201,7 +1198,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@ -1209,20 +1206,20 @@
"type": "tidelift"
}
],
"time": "2024-06-12T14:39:25+00:00"
"time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.0.2",
"version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@ -1233,7 +1230,7 @@
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@ -1265,9 +1262,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
"time": "2024-03-05T20:51:40+00:00"
"time": "2024-10-08T18:51:32+00:00"
},
{
"name": "phar-io/manifest",
@ -1389,16 +1386,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.11.2",
"version": "1.11.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "0d5d4294a70deb7547db655c47685d680e39cfec"
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec",
"reference": "0d5d4294a70deb7547db655c47685d680e39cfec",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
"shasum": ""
},
"require": {
@ -1443,25 +1440,25 @@
"type": "github"
}
],
"time": "2024-05-24T13:23:04+00:00"
"time": "2024-08-19T14:37:29+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.3.16",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "d5242a59d035e46774f2e634b374bc39ff62cb95"
"reference": "f3ea021866f4263f07ca3636bf22c64be9610c11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95",
"reference": "d5242a59d035e46774f2e634b374bc39ff62cb95",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11",
"reference": "f3ea021866f4263f07ca3636bf22c64be9610c11",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10"
"phpstan/phpstan": "^1.11"
},
"conflict": {
"phpunit/phpunit": "<7.0"
@ -1493,27 +1490,27 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0"
},
"time": "2024-02-23T09:51:20+00:00"
"time": "2024-04-20T06:39:00+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.5.5",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "2e193a07651a6f4be3baa44ddb21d822681f5918"
"reference": "363f921dd8441777d4fc137deb99beb486c77df1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2e193a07651a6f4be3baa44ddb21d822681f5918",
"reference": "2e193a07651a6f4be3baa44ddb21d822681f5918",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1",
"reference": "363f921dd8441777d4fc137deb99beb486c77df1",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10.60"
"phpstan/phpstan": "^1.11"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@ -1542,38 +1539,38 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.5"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0"
},
"time": "2024-04-19T15:12:26+00:00"
"time": "2024-04-20T06:37:51+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.15",
"version": "10.1.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae"
"reference": "7e308268858ed6baedc8704a304727d20bc07c77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
"reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
"reference": "7e308268858ed6baedc8704a304727d20bc07c77",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.18 || ^5.0",
"nikic/php-parser": "^4.19.1 || ^5.1.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
"sebastian/code-unit-reverse-lookup": "^3.0",
"sebastian/complexity": "^3.0",
"sebastian/environment": "^6.0",
"sebastian/lines-of-code": "^2.0",
"sebastian/version": "^4.0",
"theseer/tokenizer": "^1.2.0"
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-text-template": "^3.0.1",
"sebastian/code-unit-reverse-lookup": "^3.0.0",
"sebastian/complexity": "^3.2.0",
"sebastian/environment": "^6.1.0",
"sebastian/lines-of-code": "^2.0.2",
"sebastian/version": "^4.0.1",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^10.1"
@ -1585,7 +1582,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.1-dev"
"dev-main": "10.1.x-dev"
}
},
"autoload": {
@ -1614,7 +1611,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
},
"funding": [
{
@ -1622,7 +1619,7 @@
"type": "github"
}
],
"time": "2024-06-29T08:25:15+00:00"
"time": "2024-08-22T04:31:57+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1869,16 +1866,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.24",
"version": "10.5.38",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015"
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015",
"reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"shasum": ""
},
"require": {
@ -1888,26 +1885,26 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.10.1",
"phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
"myclabs/deep-copy": "^1.12.0",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
"phpunit/php-code-coverage": "^10.1.5",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-invoker": "^4.0",
"phpunit/php-text-template": "^3.0",
"phpunit/php-timer": "^6.0",
"sebastian/cli-parser": "^2.0",
"sebastian/code-unit": "^2.0",
"sebastian/comparator": "^5.0",
"sebastian/diff": "^5.0",
"sebastian/environment": "^6.0",
"sebastian/exporter": "^5.1",
"sebastian/global-state": "^6.0.1",
"sebastian/object-enumerator": "^5.0",
"sebastian/recursion-context": "^5.0",
"sebastian/type": "^4.0",
"sebastian/version": "^4.0"
"phpunit/php-code-coverage": "^10.1.16",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-invoker": "^4.0.0",
"phpunit/php-text-template": "^3.0.1",
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.3",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.2",
"sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.0",
"sebastian/type": "^4.0.0",
"sebastian/version": "^4.0.1"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files"
@ -1950,7 +1947,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.24"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
},
"funding": [
{
@ -1966,7 +1963,7 @@
"type": "tidelift"
}
],
"time": "2024-06-20T13:09:54+00:00"
"time": "2024-10-28T13:06:21+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2138,16 +2135,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.1",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "2db5010a484d53ebf536087a70b4a5423c102372"
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372",
"reference": "2db5010a484d53ebf536087a70b4a5423c102372",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"shasum": ""
},
"require": {
@ -2158,7 +2155,7 @@
"sebastian/exporter": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.3"
"phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@ -2203,7 +2200,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
},
"funding": [
{
@ -2211,7 +2208,7 @@
"type": "github"
}
],
"time": "2023-08-14T13:18:12+00:00"
"time": "2024-10-18T14:56:07+00:00"
},
{
"name": "sebastian/complexity",

View File

@ -40,6 +40,7 @@ parameters:
- build/php
dynamicConstantNames:
- pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD
- pocketmine\VersionInfo::BUILD_CHANNEL
- pocketmine\DEBUG
- pocketmine\IS_DEVELOPMENT_BUILD
stubFiles:
@ -47,8 +48,6 @@ parameters:
- tests/phpstan/stubs/leveldb.stub
- tests/phpstan/stubs/pmmpthread.stub
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
staticReflectionClassNamePatterns:
- "#^COM$#"
typeAliases:
#variadics don't work for this - mixed probably shouldn't work either, but for now it does
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that,

View File

@ -54,12 +54,6 @@ memory:
#This only affects the main thread. Other threads should fire their own collections
period: 36000
#Fire asynchronous tasks to collect garbage from workers
collect-async-worker: true
#Trigger on low memory
low-memory-trigger: true
#Settings controlling memory dump handling.
memory-dump:
#Dump memory from async workers as well as the main thread. If you have issues with segfaults when dumping memory, disable this setting.
@ -69,16 +63,6 @@ memory:
#Cap maximum render distance per player when low memory is triggered. Set to 0 to disable cap.
chunk-radius: 4
#Do chunk garbage collection on trigger
trigger-chunk-collect: true
world-caches:
#Disallow adding to world chunk-packet caches when memory is low
disable-chunk-cache: true
#Clear world caches when memory is low
low-memory-trigger: true
network:
#Threshold for batching packets, in bytes. Only these packets will be compressed
#Set to 0 to compress everything, -1 to disable.

View File

@ -0,0 +1,103 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine;
use pocketmine\timings\TimingsHandler;
use function gc_collect_cycles;
use function gc_disable;
use function gc_status;
use function hrtime;
use function max;
use function min;
use function number_format;
/**
* Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector,
* but triggered at a time that suits the thread instead of in random code pathways.
*
* The GC trigger behaviour in this class was adapted from Zend/zend_gc.c as of PHP 8.3.14.
*/
final class GarbageCollectorManager{
//TODO: These values could be adjusted to better suit PM, but for now we just want to mirror PHP GC to minimize
//behavioural changes.
private const GC_THRESHOLD_TRIGGER = 100;
private const GC_THRESHOLD_MAX = 1_000_000_000;
private const GC_THRESHOLD_DEFAULT = 10_001;
private const GC_THRESHOLD_STEP = 10_000;
private int $threshold = self::GC_THRESHOLD_DEFAULT;
private int $collectionTimeTotalNs = 0;
private \Logger $logger;
private TimingsHandler $timings;
public function __construct(
\Logger $logger,
?TimingsHandler $parentTimings,
){
gc_disable();
$this->logger = new \PrefixedLogger($logger, "Cyclic Garbage Collector");
$this->timings = new TimingsHandler("Cyclic Garbage Collector", $parentTimings);
}
private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{
//TODO Very simple heuristic for dynamic GC buffer resizing:
//If there are "too few" collections, increase the collection threshold
//by a fixed step
//Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14
if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->threshold){
$this->threshold = min(self::GC_THRESHOLD_MAX, $this->threshold + self::GC_THRESHOLD_STEP);
}elseif($this->threshold > self::GC_THRESHOLD_DEFAULT){
$this->threshold = max(self::GC_THRESHOLD_DEFAULT, $this->threshold - self::GC_THRESHOLD_STEP);
}
}
public function getThreshold() : int{ return $this->threshold; }
public function getCollectionTimeTotalNs() : int{ return $this->collectionTimeTotalNs; }
public function maybeCollectCycles() : int{
$rootsBefore = gc_status()["roots"];
if($rootsBefore < $this->threshold){
return 0;
}
$this->timings->startTiming();
$start = hrtime(true);
$cycles = gc_collect_cycles();
$end = hrtime(true);
$rootsAfter = gc_status()["roots"];
$this->adjustGcThreshold($cycles, $rootsAfter);
$this->timings->stopTiming();
$time = $end - $start;
$this->collectionTimeTotalNs += $time;
$this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns");
return $cycles;
}
}

305
src/MemoryDump.php Normal file
View File

@ -0,0 +1,305 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function arsort;
use function count;
use function fclose;
use function file_exists;
use function file_put_contents;
use function fopen;
use function fwrite;
use function gc_disable;
use function gc_enable;
use function gc_enabled;
use function get_class;
use function get_declared_classes;
use function get_defined_functions;
use function ini_get;
use function ini_set;
use function is_array;
use function is_float;
use function is_object;
use function is_resource;
use function is_string;
use function json_encode;
use function mkdir;
use function print_r;
use function spl_object_hash;
use function strlen;
use function substr;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
use const SORT_NUMERIC;
final class MemoryDump{
private function __construct(){
//NOOP
}
/**
* Static memory dumper accessible from any thread.
*/
public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
ini_set('memory_limit', '-1');
$gcEnabled = gc_enabled();
gc_disable();
if(!file_exists($outputFolder)){
mkdir($outputFolder, 0777, true);
}
$obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+"));
$objects = [];
$refCounts = [];
$instanceCounts = [];
$staticProperties = [];
$staticCount = 0;
$functionStaticVars = [];
$functionStaticVarsCount = 0;
foreach(get_declared_classes() as $className){
$reflection = new \ReflectionClass($className);
$staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){
if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){
continue;
}
if(!$property->isInitialized()){
continue;
}
$staticCount++;
$staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($staticProperties[$className]) === 0){
unset($staticProperties[$className]);
}
foreach($reflection->getMethods() as $method){
if($method->getDeclaringClass()->getName() !== $reflection->getName()){
continue;
}
$methodStatics = [];
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
$functionStaticVars[$className . "::" . $method->getName()] = $methodStatics;
$functionStaticVarsCount += count($functionStaticVars);
}
}
}
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $staticCount static properties");
$globalVariables = [];
$globalCount = 0;
$ignoredGlobals = [
'GLOBALS' => true,
'_SERVER' => true,
'_REQUEST' => true,
'_POST' => true,
'_GET' => true,
'_FILES' => true,
'_ENV' => true,
'_COOKIE' => true,
'_SESSION' => true
];
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
$globalCount++;
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $globalCount global variables");
foreach(get_defined_functions()["user"] as $function){
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
$functionStaticVars[$function] = $vars;
$functionStaticVarsCount += count($vars);
}
}
file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $functionStaticVarsCount function static variables");
$data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
do{
$continue = false;
foreach(Utils::stringifyKeys($objects) as $hash => $object){
if(!is_object($object)){
continue;
}
$continue = true;
$className = get_class($object);
if(!isset($instanceCounts[$className])){
$instanceCounts[$className] = 1;
}else{
$instanceCounts[$className]++;
}
$objects[$hash] = true;
$info = [
"information" => "$hash@$className",
];
if($object instanceof \Closure){
$info["definition"] = Utils::getNiceClosureName($object);
$info["referencedVars"] = [];
$reflect = new \ReflectionFunction($object);
if(($closureThis = $reflect->getClosureThis()) !== null){
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
$reflection = new \ReflectionObject($object);
$info["properties"] = [];
for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
foreach($reflection->getProperties() as $property){
if($property->isStatic()){
continue;
}
$name = $property->getName();
if($reflection !== $original){
if($property->isPrivate()){
$name = $reflection->getName() . ":" . $name;
}else{
continue;
}
}
if(!$property->isInitialized($object)){
continue;
}
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}
}
fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
}
}while($continue);
$logger->info("Wrote " . count($objects) . " objects");
fclose($obData);
file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
arsort($instanceCounts, SORT_NUMERIC);
file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Finished!");
ini_set('memory_limit', $hardLimit);
if($gcEnabled){
gc_enable();
}
}
/**
* @param object[] $objects reference parameter
* @param int[] $refCounts reference parameter
*
* @phpstan-param array<string, object> $objects
* @phpstan-param array<string, int> $refCounts
* @phpstan-param-out array<string, object> $objects
* @phpstan-param-out array<string, int> $refCounts
*/
private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{
if($maxNesting <= 0){
return "(error) NESTING LIMIT REACHED";
}
--$maxNesting;
if(is_object($from)){
if(!isset($objects[$hash = spl_object_hash($from)])){
$objects[$hash] = $from;
$refCounts[$hash] = 0;
}
++$refCounts[$hash];
$data = "(object) $hash";
}elseif(is_array($from)){
if($recursion >= 5){
return "(error) ARRAY RECURSION LIMIT REACHED";
}
$data = [];
$numeric = 0;
foreach(Utils::promoteKeys($from) as $key => $value){
$data[$numeric] = [
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
];
$numeric++;
}
}elseif(is_string($from)){
$data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
}elseif(is_resource($from)){
$data = "(resource) " . print_r($from, true);
}elseif(is_float($from)){
$data = "(float) $from";
}else{
$data = $from;
}
return $data;
}
}

View File

@ -29,52 +29,24 @@ use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\timings\Timings;
use pocketmine\utils\Process;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties as Yml;
use Symfony\Component\Filesystem\Path;
use function arsort;
use function count;
use function fclose;
use function file_exists;
use function file_put_contents;
use function fopen;
use function fwrite;
use function gc_collect_cycles;
use function gc_disable;
use function gc_enable;
use function gc_mem_caches;
use function get_class;
use function get_declared_classes;
use function get_defined_functions;
use function ini_get;
use function ini_set;
use function intdiv;
use function is_array;
use function is_float;
use function is_object;
use function is_resource;
use function is_string;
use function json_encode;
use function mb_strtoupper;
use function min;
use function mkdir;
use function preg_match;
use function print_r;
use function round;
use function spl_object_hash;
use function sprintf;
use function strlen;
use function substr;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
use const SORT_NUMERIC;
class MemoryManager{
private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND;
private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2;
private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND;
private GarbageCollectorManager $cycleGcManager;
private int $memoryLimit;
private int $globalMemoryLimit;
private int $checkRate;
@ -88,14 +60,8 @@ class MemoryManager{
private int $garbageCollectionPeriod;
private int $garbageCollectionTicker = 0;
private bool $garbageCollectionTrigger;
private bool $garbageCollectionAsync;
private int $lowMemChunkRadiusOverride;
private bool $lowMemChunkGC;
private bool $lowMemDisableChunkCache;
private bool $lowMemClearWorldCache;
private bool $dumpWorkers = true;
@ -105,6 +71,7 @@ class MemoryManager{
private Server $server
){
$this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager");
$this->cycleGcManager = new GarbageCollectorManager($this->logger, Timings::$memoryManager);
$this->init($server->getConfigGroup());
}
@ -142,17 +109,10 @@ class MemoryManager{
$this->continuousTriggerRate = $config->getPropertyInt(Yml::MEMORY_CONTINUOUS_TRIGGER_RATE, self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
$this->garbageCollectionPeriod = $config->getPropertyInt(Yml::MEMORY_GARBAGE_COLLECTION_PERIOD, self::DEFAULT_TICKS_PER_GC);
$this->garbageCollectionTrigger = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER, true);
$this->garbageCollectionAsync = $config->getPropertyBool(Yml::MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER, true);
$this->lowMemChunkRadiusOverride = $config->getPropertyInt(Yml::MEMORY_MAX_CHUNKS_CHUNK_RADIUS, 4);
$this->lowMemChunkGC = $config->getPropertyBool(Yml::MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT, true);
$this->lowMemDisableChunkCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE, true);
$this->lowMemClearWorldCache = $config->getPropertyBool(Yml::MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER, true);
$this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, true);
gc_enable();
}
public function isLowMemory() : bool{
@ -163,8 +123,11 @@ class MemoryManager{
return $this->globalMemoryLimit;
}
/**
* @deprecated
*/
public function canUseChunkCache() : bool{
return !$this->lowMemory || !$this->lowMemDisableChunkCache;
return !$this->lowMemory;
}
/**
@ -180,26 +143,19 @@ class MemoryManager{
public function trigger(int $memory, int $limit, bool $global = false, int $triggerCount = 0) : void{
$this->logger->debug(sprintf("%sLow memory triggered, limit %gMB, using %gMB",
$global ? "Global " : "", round(($limit / 1024) / 1024, 2), round(($memory / 1024) / 1024, 2)));
if($this->lowMemClearWorldCache){
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->clearCache(true);
}
ChunkCache::pruneCaches();
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->clearCache(true);
}
ChunkCache::pruneCaches();
if($this->lowMemChunkGC){
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->doChunkGarbageCollection();
}
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->doChunkGarbageCollection();
}
$ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount);
$ev->call();
$cycles = 0;
if($this->garbageCollectionTrigger){
$cycles = $this->triggerGarbageCollector();
}
$cycles = $this->triggerGarbageCollector();
$this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2)));
}
@ -239,6 +195,8 @@ class MemoryManager{
if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){
$this->garbageCollectionTicker = 0;
$this->triggerGarbageCollector();
}else{
$this->cycleGcManager->maybeCollectCycles();
}
Timings::$memoryManager->stopTiming();
@ -247,14 +205,12 @@ class MemoryManager{
public function triggerGarbageCollector() : int{
Timings::$garbageCollector->startTiming();
if($this->garbageCollectionAsync){
$pool = $this->server->getAsyncPool();
if(($w = $pool->shutdownUnusedWorkers()) > 0){
$this->logger->debug("Shut down $w idle async pool workers");
}
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
}
$pool = $this->server->getAsyncPool();
if(($w = $pool->shutdownUnusedWorkers()) > 0){
$this->logger->debug("Shut down $w idle async pool workers");
}
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
}
$cycles = gc_collect_cycles();
@ -271,7 +227,7 @@ class MemoryManager{
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize) : void{
$logger = new \PrefixedLogger($this->server->getLogger(), "Memory Dump");
$logger->notice("After the memory dump is done, the server might crash");
self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
MemoryDump::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
if($this->dumpWorkers){
$pool = $this->server->getAsyncPool();
@ -283,239 +239,10 @@ class MemoryManager{
/**
* Static memory dumper accessible from any thread.
* @deprecated
* @see MemoryDump
*/
public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
ini_set('memory_limit', '-1');
gc_disable();
if(!file_exists($outputFolder)){
mkdir($outputFolder, 0777, true);
}
$obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+"));
$objects = [];
$refCounts = [];
$instanceCounts = [];
$staticProperties = [];
$staticCount = 0;
$functionStaticVars = [];
$functionStaticVarsCount = 0;
foreach(get_declared_classes() as $className){
$reflection = new \ReflectionClass($className);
$staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){
if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){
continue;
}
if(!$property->isInitialized()){
continue;
}
$staticCount++;
$staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($staticProperties[$className]) === 0){
unset($staticProperties[$className]);
}
foreach($reflection->getMethods() as $method){
if($method->getDeclaringClass()->getName() !== $reflection->getName()){
continue;
}
$methodStatics = [];
foreach($method->getStaticVariables() as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
$functionStaticVars[$className . "::" . $method->getName()] = $methodStatics;
$functionStaticVarsCount += count($functionStaticVars);
}
}
}
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $staticCount static properties");
$globalVariables = [];
$globalCount = 0;
$ignoredGlobals = [
'GLOBALS' => true,
'_SERVER' => true,
'_REQUEST' => true,
'_POST' => true,
'_GET' => true,
'_FILES' => true,
'_ENV' => true,
'_COOKIE' => true,
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
$globalCount++;
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $globalCount global variables");
foreach(get_defined_functions()["user"] as $function){
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach($reflect->getStaticVariables() as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
$functionStaticVars[$function] = $vars;
$functionStaticVarsCount += count($vars);
}
}
file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $functionStaticVarsCount function static variables");
$data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
do{
$continue = false;
foreach(Utils::stringifyKeys($objects) as $hash => $object){
if(!is_object($object)){
continue;
}
$continue = true;
$className = get_class($object);
if(!isset($instanceCounts[$className])){
$instanceCounts[$className] = 1;
}else{
$instanceCounts[$className]++;
}
$objects[$hash] = true;
$info = [
"information" => "$hash@$className",
];
if($object instanceof \Closure){
$info["definition"] = Utils::getNiceClosureName($object);
$info["referencedVars"] = [];
$reflect = new \ReflectionFunction($object);
if(($closureThis = $reflect->getClosureThis()) !== null){
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
foreach($reflect->getStaticVariables() as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
$reflection = new \ReflectionObject($object);
$info["properties"] = [];
for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
foreach($reflection->getProperties() as $property){
if($property->isStatic()){
continue;
}
$name = $property->getName();
if($reflection !== $original){
if($property->isPrivate()){
$name = $reflection->getName() . ":" . $name;
}else{
continue;
}
}
if(!$property->isInitialized($object)){
continue;
}
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}
}
fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
}
}while($continue);
$logger->info("Wrote " . count($objects) . " objects");
fclose($obData);
file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
arsort($instanceCounts, SORT_NUMERIC);
file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Finished!");
ini_set('memory_limit', $hardLimit);
gc_enable();
}
/**
* @param object[] $objects reference parameter
* @param int[] $refCounts reference parameter
*
* @phpstan-param array<string, object> $objects
* @phpstan-param array<string, int> $refCounts
* @phpstan-param-out array<string, object> $objects
* @phpstan-param-out array<string, int> $refCounts
*/
private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{
if($maxNesting <= 0){
return "(error) NESTING LIMIT REACHED";
}
--$maxNesting;
if(is_object($from)){
if(!isset($objects[$hash = spl_object_hash($from)])){
$objects[$hash] = $from;
$refCounts[$hash] = 0;
}
++$refCounts[$hash];
$data = "(object) $hash";
}elseif(is_array($from)){
if($recursion >= 5){
return "(error) ARRAY RECURSION LIMIT REACHED";
}
$data = [];
$numeric = 0;
foreach($from as $key => $value){
$data[$numeric] = [
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
];
$numeric++;
}
}elseif(is_string($from)){
$data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
}elseif(is_resource($from)){
$data = "(resource) " . print_r($from, true);
}elseif(is_float($from)){
$data = "(float) $from";
}else{
$data = $from;
}
return $data;
MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger);
}
}

View File

@ -282,6 +282,11 @@ JIT_WARNING
exit(0);
}
if(defined('pocketmine\ORIGINAL_PHAR_PATH')){
//if we're inside a phar cache, \pocketmine\PATH will not include the original phar
Filesystem::addCleanedPath(ORIGINAL_PHAR_PATH, Filesystem::CLEAN_PATH_SRC_PREFIX);
}
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
$dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
$pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";

View File

@ -89,6 +89,8 @@ use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\TimingsCollectionTask;
use pocketmine\scheduler\TimingsControlTask;
use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger;
@ -736,12 +738,15 @@ class Server{
/**
* @return string[][]
* @phpstan-return array<string, list<string>>
*/
public function getCommandAliases() : array{
$section = $this->configGroup->getProperty(Yml::ALIASES);
$result = [];
if(is_array($section)){
foreach($section as $key => $value){
foreach(Utils::promoteKeys($section) as $key => $value){
//TODO: more validation needed here
//key might not be a string, value might not be list<string>
$commands = [];
if(is_array($value)){
$commands = $value;
@ -749,7 +754,7 @@ class Server{
$commands[] = (string) $value;
}
$result[$key] = $commands;
$result[(string) $key] = $commands;
}
}
@ -891,7 +896,36 @@ class Server{
$poolSize = max(1, (int) $poolSize);
}
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
$this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$this->asyncPool->addWorkerStartHook(function(int $i) : void{
if(TimingsHandler::isEnabled()){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i);
}
});
TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
}
});
TimingsHandler::getReloadCallbacks()->add(function() : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
}
});
TimingsHandler::getCollectCallbacks()->add(function() : array{
$promises = [];
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$resolver = new PromiseResolver();
$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);
$promises[] = $resolver->getPromise();
}
return $promises;
});
$netCompressionThreshold = -1;
if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@ -965,9 +999,6 @@ class Server{
)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
DefaultPermissions::registerCorePermissions();
$this->commandMap = new SimpleCommandMap($this);
@ -1094,7 +1125,11 @@ class Server{
$anyWorldFailedToLoad = false;
foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
if(!is_string($name)){
//TODO: this probably should be an error
continue;
}
if($options === null){
$options = [];
}elseif(!is_array($options)){

View File

@ -31,8 +31,8 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.17.0";
public const IS_DEVELOPMENT_BUILD = false;
public const BASE_VERSION = "5.23.3";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";
/**

View File

@ -75,20 +75,14 @@ final class YmlServerProperties{
public const MEMORY_CONTINUOUS_TRIGGER = 'memory.continuous-trigger';
public const MEMORY_CONTINUOUS_TRIGGER_RATE = 'memory.continuous-trigger-rate';
public const MEMORY_GARBAGE_COLLECTION = 'memory.garbage-collection';
public const MEMORY_GARBAGE_COLLECTION_COLLECT_ASYNC_WORKER = 'memory.garbage-collection.collect-async-worker';
public const MEMORY_GARBAGE_COLLECTION_LOW_MEMORY_TRIGGER = 'memory.garbage-collection.low-memory-trigger';
public const MEMORY_GARBAGE_COLLECTION_PERIOD = 'memory.garbage-collection.period';
public const MEMORY_GLOBAL_LIMIT = 'memory.global-limit';
public const MEMORY_MAIN_HARD_LIMIT = 'memory.main-hard-limit';
public const MEMORY_MAIN_LIMIT = 'memory.main-limit';
public const MEMORY_MAX_CHUNKS = 'memory.max-chunks';
public const MEMORY_MAX_CHUNKS_CHUNK_RADIUS = 'memory.max-chunks.chunk-radius';
public const MEMORY_MAX_CHUNKS_TRIGGER_CHUNK_COLLECT = 'memory.max-chunks.trigger-chunk-collect';
public const MEMORY_MEMORY_DUMP = 'memory.memory-dump';
public const MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER = 'memory.memory-dump.dump-async-worker';
public const MEMORY_WORLD_CACHES = 'memory.world-caches';
public const MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE = 'memory.world-caches.disable-chunk-cache';
public const MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER = 'memory.world-caches.low-memory-trigger';
public const NETWORK = 'network';
public const NETWORK_ASYNC_COMPRESSION = 'network.async-compression';
public const NETWORK_ASYNC_COMPRESSION_THRESHOLD = 'network.async-compression-threshold';

View File

@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\AnvilFallSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
use function round;
class Anvil extends Transparent implements Fallable{
@ -91,13 +91,13 @@ class Anvil extends Transparent implements Fallable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$this->facing = Facing::rotateY($player->getHorizontalFacing(), true);
$this->facing = Facing::rotateY($player->getHorizontalFacing(), false);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onHitGround(FallingBlock $blockEntity) : bool{
if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if($this->damage !== self::VERY_DAMAGED){
$this->damage = $this->damage + 1;
}else{

View File

@ -362,6 +362,8 @@ class Block{
*
* A replacement block may be returned. This is useful if the block type changed due to reading of world data (e.g.
* data from a block entity).
*
* @phpstan-impure
*/
public function readStateFromWorld() : Block{
return $this;

View File

@ -745,8 +745,49 @@ final class BlockTypeIds{
public const PITCHER_PLANT = 10715;
public const PITCHER_CROP = 10716;
public const DOUBLE_PITCHER_CROP = 10717;
public const CAMPFIRE = 10718;
public const SOUL_CAMPFIRE = 10719;
public const TUFF_SLAB = 10720;
public const TUFF_STAIRS = 10721;
public const TUFF_WALL = 10722;
public const CHISELED_TUFF = 10723;
public const TUFF_BRICKS = 10724;
public const TUFF_BRICK_SLAB = 10725;
public const TUFF_BRICK_STAIRS = 10726;
public const TUFF_BRICK_WALL = 10727;
public const CHISELED_TUFF_BRICKS = 10728;
public const POLISHED_TUFF = 10729;
public const POLISHED_TUFF_SLAB = 10730;
public const POLISHED_TUFF_STAIRS = 10731;
public const POLISHED_TUFF_WALL = 10732;
public const COPPER_BULB = 10733;
public const COPPER_DOOR = 10734;
public const COPPER_TRAPDOOR = 10735;
public const CHISELED_COPPER = 10736;
public const COPPER_GRATE = 10737;
public const PALE_OAK_BUTTON = 10738;
public const PALE_OAK_DOOR = 10739;
public const PALE_OAK_FENCE = 10740;
public const PALE_OAK_FENCE_GATE = 10741;
public const PALE_OAK_LEAVES = 10742;
public const PALE_OAK_LOG = 10743;
public const PALE_OAK_PLANKS = 10744;
public const PALE_OAK_PRESSURE_PLATE = 10745;
public const PALE_OAK_SIGN = 10746;
public const PALE_OAK_SLAB = 10747;
public const PALE_OAK_STAIRS = 10748;
public const PALE_OAK_TRAPDOOR = 10749;
public const PALE_OAK_WALL_SIGN = 10750;
public const PALE_OAK_WOOD = 10751;
public const RESIN = 10752;
public const RESIN_BRICK_SLAB = 10753;
public const RESIN_BRICK_STAIRS = 10754;
public const RESIN_BRICK_WALL = 10755;
public const RESIN_BRICKS = 10756;
public const RESIN_CLUMP = 10757;
public const CHISELED_RESIN_BRICKS = 10758;
public const FIRST_UNUSED_BLOCK_ID = 10718;
public const FIRST_UNUSED_BLOCK_ID = 10759;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;

View File

@ -27,10 +27,6 @@ use pocketmine\item\Item;
class BlueIce extends Opaque{
public function getLightLevel() : int{
return 1;
}
public function getFrictionFactor() : float{
return 0.99;
}
@ -38,4 +34,8 @@ class BlueIce extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
}

View File

@ -52,6 +52,9 @@ class CakeWithCandle extends BaseCake{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($this->lit && $face !== Facing::UP){
return true;
}
if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){
return true;
}

285
src/block/Campfire.php Normal file
View File

@ -0,0 +1,285 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\inventory\CampfireInventory;
use pocketmine\block\tile\Campfire as TileCampfire;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\LightableTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\crafting\FurnaceRecipe;
use pocketmine\crafting\FurnaceType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\entity\projectile\Projectile;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\event\block\CampfireCookEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\PotionType;
use pocketmine\item\Shovel;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\BlazeShootSound;
use pocketmine\world\sound\FireExtinguishSound;
use pocketmine\world\sound\FlintSteelSound;
use pocketmine\world\sound\ItemFrameAddItemSound;
use function count;
use function min;
use function mt_rand;
class Campfire extends Transparent{
use HorizontalFacingTrait{
HorizontalFacingTrait::describeBlockOnlyState as encodeFacingState;
}
use LightableTrait{
LightableTrait::describeBlockOnlyState as encodeLitState;
}
private const UPDATE_INTERVAL_TICKS = 10;
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
protected CampfireInventory $inventory;
/**
* @var int[] slot => ticks
* @phpstan-var array<int, int>
*/
protected array $cookingTimes = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$this->encodeFacingState($w);
$this->encodeLitState($w);
}
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileCampfire){
$this->inventory = $tile->getInventory();
$this->cookingTimes = $tile->getCookingTimes();
}else{
$this->inventory = new CampfireInventory($this->position);
}
return $this;
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileCampfire){
$tile->setCookingTimes($this->cookingTimes);
}
}
public function hasEntityCollision() : bool{
return true;
}
public function getLightLevel() : int{
return $this->lit ? 15 : 0;
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::CHARCOAL()->setCount(2)
];
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
}
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
public function getInventory() : CampfireInventory{
return $this->inventory;
}
protected function getFurnaceType() : FurnaceType{
return FurnaceType::CAMPFIRE;
}
protected function getEntityCollisionDamage() : int{
return 1;
}
/**
* Sets the number of ticks during the item in the given slot has been cooked.
*/
public function setCookingTime(int $slot, int $time) : void{
if($slot < 0 || $slot > 3){
throw new \InvalidArgumentException("Slot must be in range 0-3");
}
if($time < 0 || $time > $this->getFurnaceType()->getCookDurationTicks()){
throw new \InvalidArgumentException("CookingTime must be in range 0-" . $this->getFurnaceType()->getCookDurationTicks());
}
$this->cookingTimes[$slot] = $time;
}
/**
* Returns the number of ticks during the item in the given slot has been cooked.
*/
public function getCookingTime(int $slot) : int{
return $this->cookingTimes[$slot] ?? 0;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->getSide(Facing::DOWN) instanceof Campfire){
return false;
}
if($player !== null){
$this->facing = $player->getHorizontalFacing();
}
$this->lit = true;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if(!$this->lit){
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
$item->pop();
$this->ignite();
$this->position->getWorld()->addSound($this->position, new BlazeShootSound());
return true;
}elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
if($item instanceof Durable){
$item->applyDamage(1);
}
$this->ignite();
return true;
}
}elseif($item instanceof Shovel){
$item->applyDamage(1);
$this->extinguish();
return true;
}
if($this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){
$ingredient = clone $item;
$ingredient->setCount(1);
if(count($this->inventory->addItem($ingredient)) === 0){
$item->pop();
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
return true;
}
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->lit && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){
$this->extinguish();
//TODO: Waterlogging
}
}
public function onEntityInside(Entity $entity) : bool{
if(!$this->lit){
if($entity->isOnFire()){
$this->ignite();
return false;
}
}elseif($entity instanceof Living){
$entity->attack(new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, $this->getEntityCollisionDamage()));
}
return true;
}
public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
if($this->lit && $projectile instanceof SplashPotion && $projectile->getPotionType() === PotionType::WATER){
$this->extinguish();
}
}
public function onScheduledUpdate() : void{
if($this->lit){
$items = $this->inventory->getContents();
$furnaceType = $this->getFurnaceType();
$maxCookDuration = $furnaceType->getCookDurationTicks();
foreach($items as $slot => $item){
$this->setCookingTime($slot, min($maxCookDuration, $this->getCookingTime($slot) + self::UPDATE_INTERVAL_TICKS));
if($this->getCookingTime($slot) >= $maxCookDuration){
$result =
($recipe = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($item)) instanceof FurnaceRecipe ?
$recipe->getResult() :
VanillaItems::AIR();
$ev = new CampfireCookEvent($this, $slot, $item, $result);
$ev->call();
if ($ev->isCancelled()){
continue;
}
$this->inventory->setItem($slot, VanillaItems::AIR());
$this->setCookingTime($slot, 0);
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult());
}
}
if(count($items) > 0){
$this->position->getWorld()->setBlock($this->position, $this);
}
if(mt_rand(1, 6) === 1){
$this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound());
}
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
}
}
private function extinguish() : void{
$this->position->getWorld()->addSound($this->position, new FireExtinguishSound());
$this->position->getWorld()->setBlock($this->position, $this->setLit(false));
}
private function ignite() : void{
$this->position->getWorld()->addSound($this->position, new FlintSteelSound());
$this->position->getWorld()->setBlock($this->position, $this->setLit(true));
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
}
}

View File

@ -48,11 +48,32 @@ class ChiseledBookshelf extends Opaque{
*/
private array $slots = [];
private ?ChiseledBookshelfSlot $lastInteractedSlot = null;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->enumSet($this->slots, ChiseledBookshelfSlot::cases());
}
public function readStateFromWorld() : Block{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileChiseledBookshelf){
$this->lastInteractedSlot = $tile->getLastInteractedSlot();
}else{
$this->lastInteractedSlot = null;
}
return $this;
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileChiseledBookshelf){
$tile->setLastInteractedSlot($this->lastInteractedSlot);
}
}
/**
* Returns whether the given slot is displayed as occupied.
* This doesn't guarantee that there is or isn't a book in the bookshelf's inventory.
@ -92,6 +113,23 @@ class ChiseledBookshelf extends Opaque{
return $this->slots;
}
/**
* Returns the last slot interacted by a player or null if no slot has been interacted with yet.
*/
public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{
return $this->lastInteractedSlot;
}
/**
* Sets the last slot interacted by a player.
*
* @return $this
*/
public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : self{
$this->lastInteractedSlot = $lastInteractedSlot;
return $this;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($face !== $this->facing){
return false;
@ -112,10 +150,12 @@ class ChiseledBookshelf extends Opaque{
$returnedItems[] = $inventory->getItem($slot->value);
$inventory->clear($slot->value);
$this->setSlot($slot, false);
$this->lastInteractedSlot = $slot;
}elseif($item instanceof WritableBookBase || $item instanceof Book || $item instanceof EnchantedBook){
//TODO: type tags like blocks would be better for this
$inventory->setItem($slot->value, $item->pop());
$this->setSlot($slot, true);
$this->lastInteractedSlot = $slot;
}else{
return true;
}

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
class Copper extends Opaque{
class Copper extends Opaque implements CopperMaterial{
use CopperTrait;
}

69
src/block/CopperBulb.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CopperTrait;
use pocketmine\block\utils\LightableTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
class CopperBulb extends Opaque implements CopperMaterial{
use CopperTrait;
use PoweredByRedstoneTrait;
use LightableTrait{
describeBlockOnlyState as encodeLitState;
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$this->encodeLitState($w);
$w->bool($this->powered);
}
/** @return $this */
public function togglePowered(bool $powered) : self{
if($powered === $this->powered){
return $this;
}
if ($powered) {
$this->setLit(!$this->lit);
}
$this->setPowered($powered);
return $this;
}
public function getLightLevel() : int{
if ($this->lit) {
return match($this->oxidation){
CopperOxidation::NONE => 15,
CopperOxidation::EXPOSED => 12,
CopperOxidation::WEATHERED => 8,
CopperOxidation::OXIDIZED => 4,
};
}
return 0;
}
}

53
src/block/CopperDoor.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class CopperDoor extends Door implements CopperMaterial{
use CopperTrait{
onInteract as onInteractCopper;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if ($player !== null && $player->isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) {
//copy copper properties to other half
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
$world = $this->position->getWorld();
if ($other instanceof CopperDoor) {
$other->setOxidation($this->oxidation);
$other->setWaxed($this->waxed);
$world->setBlock($other->position, $other);
}
return true;
}
return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
}
}

33
src/block/CopperGrate.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
class CopperGrate extends Transparent implements CopperMaterial{
use CopperTrait;
//TODO: waterlogging!
}

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
class CopperSlab extends Slab{
class CopperSlab extends Slab implements CopperMaterial{
use CopperTrait;
}

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
class CopperStairs extends Stair{
class CopperStairs extends Stair implements CopperMaterial{
use CopperTrait;
}

View File

@ -0,0 +1,44 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperTrait;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class CopperTrapdoor extends Trapdoor implements CopperMaterial{
use CopperTrait{
onInteract as onInteractCopper;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if ($player !== null && $player->isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) {
return true;
}
return parent::onInteract($item, $face, $clickVector, $player, $returnedItems);
}
}

View File

@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function intdiv;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
@ -148,7 +148,7 @@ class Farmland extends Transparent{
}
public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\SupportType;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
/**
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
@ -40,6 +41,11 @@ abstract class Flowable extends Transparent{
return false;
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return (!$this->canBeFlowedInto() || !$blockReplace instanceof Liquid) &&
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
/**
* @return AxisAlignedBB[]
*/

View File

@ -24,60 +24,20 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
use function array_key_first;
use function count;
use function shuffle;
class GlowLichen extends Transparent{
/** @var int[] */
protected array $faces = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->facingFlags($this->faces);
}
/** @return int[] */
public function getFaces() : array{ return $this->faces; }
public function hasFace(int $face) : bool{
return isset($this->faces[$face]);
}
/**
* @param int[] $faces
* @return $this
*/
public function setFaces(array $faces) : self{
$uniqueFaces = [];
foreach($faces as $face){
Facing::validate($face);
$uniqueFaces[$face] = $face;
}
$this->faces = $uniqueFaces;
return $this;
}
/** @return $this */
public function setFace(int $face, bool $value) : self{
Facing::validate($face);
if($value){
$this->faces[$face] = $face;
}else{
unset($this->faces[$face]);
}
return $this;
}
use MultiAnySupportTrait;
public function getLightLevel() : int{
return 7;
@ -102,39 +62,11 @@ class GlowLichen extends Transparent{
return true;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
$availableFaces = $this->getAvailableFaces();
if(count($availableFaces) === 0){
return false;
}
$opposite = Facing::opposite($face);
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
$this->faces[$placedFace] = $placedFace;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$changed = false;
foreach($this->faces as $face){
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
unset($this->faces[$face]);
$changed = true;
}
}
if($changed){
$world = $this->position->getWorld();
if(count($this->faces) === 0){
$world->useBreakOn($this->position);
}else{
$world->setBlock($this->position, $this);
}
}
/**
* @return int[]
*/
protected function getInitialPlaceFaces(Block $blockReplace) : array{
return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
}
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
@ -261,17 +193,4 @@ class GlowLichen extends Transparent{
public function getFlammability() : int{
return 100;
}
/**
* @return array<int, int> $faces
*/
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
$faces[$face] = $face;
}
}
return $faces;
}
}

View File

@ -31,13 +31,13 @@ use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ItemFrameAddItemSound;
use pocketmine\world\sound\ItemFrameRemoveItemSound;
use pocketmine\world\sound\ItemFrameRotateItemSound;
use function is_infinite;
use function is_nan;
use function lcg_value;
class ItemFrame extends Flowable{
use AnyFacingTrait;
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
return false;
}
$world = $this->position->getWorld();
if(lcg_value() <= $this->itemDropChance){
if(Utils::getRandomFloat() <= $this->itemDropChance){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound());
}
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
$drops[] = clone $this->framedItem;
}

View File

@ -157,6 +157,7 @@ class Leaves extends Transparent{
LeavesType::MANGROVE, //TODO: mangrove propagule
LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea
LeavesType::CHERRY => null, //TODO: cherry
LeavesType::PALE_OAK => null, //TODO: pale oak
})?->asItem();
if($sapling !== null){
$drops[] = $sapling;

View File

@ -33,9 +33,9 @@ use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
use pocketmine\world\sound\FizzSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{
public const MAX_DECAY = 7;
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
}
return true;
}

View File

@ -39,7 +39,7 @@ class Magma extends Opaque{
}
public function onEntityInside(Entity $entity) : bool{
if($entity instanceof Living && !$entity->isSneaking()){
if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev);
}

54
src/block/ResinClump.php Normal file
View File

@ -0,0 +1,54 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
final class ResinClump extends Transparent{
use MultiAnySupportTrait;
public function isSolid() : bool{
return false;
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}
public function canBeReplaced() : bool{
return true;
}
/**
* @return int[]
*/
protected function getInitialPlaceFaces(Block $blockReplace) : array{
return $blockReplace instanceof ResinClump ? $blockReplace->faces : [];
}
protected function recalculateCollisionBoxes() : array{
return [];
}
}

View File

@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
/**
* @return Block[]
* @phpstan-return array<int, Block>
*/
public function getAllKnownStates() : array{
return $this->fullList;

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\crafting\FurnaceType;
use pocketmine\item\Item;
class SoulCampfire extends Campfire{
public function getLightLevel() : int{
return $this->lit ? 10 : 0;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaBlocks::SOUL_SOIL()->asItem()
];
}
protected function getEntityCollisionDamage() : int{
return 2;
}
protected function getFurnaceType() : FurnaceType{
return FurnaceType::SOUL_CAMPFIRE;
}
}

View File

@ -36,7 +36,9 @@ use pocketmine\world\Position;
class Sugarcane extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
use StaticSupportTrait {
onNearbyBlockChange as onSupportBlockChange;
}
public const MAX_AGE = 15;
@ -97,7 +99,13 @@ class Sugarcane extends Flowable{
}
public function onRandomTick() : void{
if(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)){
$down = $this->getSide(Facing::DOWN);
if(!$down->hasSameTypeId($this)){
if(!$this->hasNearbyWater($down)){
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
return;
}
if($this->age === self::MAX_AGE){
$this->grow($this->position);
}else{
@ -123,4 +131,23 @@ class Sugarcane extends Flowable{
return false;
}
private function hasNearbyWater(Block $down) : bool{
foreach($down->getHorizontalSides() as $sideBlock){
$blockId = $sideBlock->getTypeId();
if($blockId === BlockTypeIds::WATER || $blockId === BlockTypeIds::FROSTED_ICE){
return true;
}
}
return false;
}
public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN);
if(!$down->hasSameTypeId($this) && !$this->hasNearbyWater($down)){
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
}else{
$this->onSupportBlockChange();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,263 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\BlockIdentifier as BID;
use pocketmine\block\BlockTypeIds as Ids;
use pocketmine\block\tile\Sign as TileSign;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\utils\SaplingType;
use pocketmine\block\utils\WoodType;
use pocketmine\item\VanillaItems;
/**
* All wood-like blocks have different IDs for different wood types.
*
* We can't make these dynamic, because some types of wood have different type properties (e.g. crimson and warped planks
* are not flammable, but all other planks are).
*
* In the future, it's entirely within the realm of reason that the other types of wood may differ in other ways, such
* as flammability, hardness, required tool tier, etc.
* Therefore, to stay on the safe side of Mojang, wood-like blocks have static types. This does unfortunately generate
* a lot of ugly code.
*
* @internal
*/
final class WoodLikeBlockIdHelper{
public static function getPlanksIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_PLANKS,
WoodType::SPRUCE => Ids::SPRUCE_PLANKS,
WoodType::BIRCH => Ids::BIRCH_PLANKS,
WoodType::JUNGLE => Ids::JUNGLE_PLANKS,
WoodType::ACACIA => Ids::ACACIA_PLANKS,
WoodType::DARK_OAK => Ids::DARK_OAK_PLANKS,
WoodType::MANGROVE => Ids::MANGROVE_PLANKS,
WoodType::CRIMSON => Ids::CRIMSON_PLANKS,
WoodType::WARPED => Ids::WARPED_PLANKS,
WoodType::CHERRY => Ids::CHERRY_PLANKS,
});
}
public static function getFenceIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_FENCE,
WoodType::SPRUCE => Ids::SPRUCE_FENCE,
WoodType::BIRCH => Ids::BIRCH_FENCE,
WoodType::JUNGLE => Ids::JUNGLE_FENCE,
WoodType::ACACIA => Ids::ACACIA_FENCE,
WoodType::DARK_OAK => Ids::DARK_OAK_FENCE,
WoodType::MANGROVE => Ids::MANGROVE_FENCE,
WoodType::CRIMSON => Ids::CRIMSON_FENCE,
WoodType::WARPED => Ids::WARPED_FENCE,
WoodType::CHERRY => Ids::CHERRY_FENCE,
});
}
public static function getSlabIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_SLAB,
WoodType::SPRUCE => Ids::SPRUCE_SLAB,
WoodType::BIRCH => Ids::BIRCH_SLAB,
WoodType::JUNGLE => Ids::JUNGLE_SLAB,
WoodType::ACACIA => Ids::ACACIA_SLAB,
WoodType::DARK_OAK => Ids::DARK_OAK_SLAB,
WoodType::MANGROVE => Ids::MANGROVE_SLAB,
WoodType::CRIMSON => Ids::CRIMSON_SLAB,
WoodType::WARPED => Ids::WARPED_SLAB,
WoodType::CHERRY => Ids::CHERRY_SLAB,
});
}
public static function getLogIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_LOG,
WoodType::SPRUCE => Ids::SPRUCE_LOG,
WoodType::BIRCH => Ids::BIRCH_LOG,
WoodType::JUNGLE => Ids::JUNGLE_LOG,
WoodType::ACACIA => Ids::ACACIA_LOG,
WoodType::DARK_OAK => Ids::DARK_OAK_LOG,
WoodType::MANGROVE => Ids::MANGROVE_LOG,
WoodType::CRIMSON => Ids::CRIMSON_STEM,
WoodType::WARPED => Ids::WARPED_STEM,
WoodType::CHERRY => Ids::CHERRY_LOG,
});
}
public static function getAllSidedLogIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_WOOD,
WoodType::SPRUCE => Ids::SPRUCE_WOOD,
WoodType::BIRCH => Ids::BIRCH_WOOD,
WoodType::JUNGLE => Ids::JUNGLE_WOOD,
WoodType::ACACIA => Ids::ACACIA_WOOD,
WoodType::DARK_OAK => Ids::DARK_OAK_WOOD,
WoodType::MANGROVE => Ids::MANGROVE_WOOD,
WoodType::CRIMSON => Ids::CRIMSON_HYPHAE,
WoodType::WARPED => Ids::WARPED_HYPHAE,
WoodType::CHERRY => Ids::CHERRY_WOOD,
});
}
public static function getLeavesIdentifier(LeavesType $leavesType) : BID{
return new BID(match($leavesType){
LeavesType::OAK => Ids::OAK_LEAVES,
LeavesType::SPRUCE => Ids::SPRUCE_LEAVES,
LeavesType::BIRCH => Ids::BIRCH_LEAVES,
LeavesType::JUNGLE => Ids::JUNGLE_LEAVES,
LeavesType::ACACIA => Ids::ACACIA_LEAVES,
LeavesType::DARK_OAK => Ids::DARK_OAK_LEAVES,
LeavesType::MANGROVE => Ids::MANGROVE_LEAVES,
LeavesType::AZALEA => Ids::AZALEA_LEAVES,
LeavesType::FLOWERING_AZALEA => Ids::FLOWERING_AZALEA_LEAVES,
LeavesType::CHERRY => Ids::CHERRY_LEAVES,
});
}
public static function getSaplingIdentifier(SaplingType $treeType) : BID{
return new BID(match($treeType){
SaplingType::OAK => Ids::OAK_SAPLING,
SaplingType::SPRUCE => Ids::SPRUCE_SAPLING,
SaplingType::BIRCH => Ids::BIRCH_SAPLING,
SaplingType::JUNGLE => Ids::JUNGLE_SAPLING,
SaplingType::ACACIA => Ids::ACACIA_SAPLING,
SaplingType::DARK_OAK => Ids::DARK_OAK_SAPLING,
});
}
/**
* @return BID[]|\Closure[]
* @phpstan-return array{BID, BID, \Closure() : \pocketmine\item\Item}
*/
public static function getSignInfo(WoodType $treeType) : array{
$make = fn(int $floorId, int $wallId, \Closure $getItem) => [
new BID($floorId, TileSign::class),
new BID($wallId, TileSign::class),
$getItem
];
return match($treeType){
WoodType::OAK => $make(Ids::OAK_SIGN, Ids::OAK_WALL_SIGN, fn() => VanillaItems::OAK_SIGN()),
WoodType::SPRUCE => $make(Ids::SPRUCE_SIGN, Ids::SPRUCE_WALL_SIGN, fn() => VanillaItems::SPRUCE_SIGN()),
WoodType::BIRCH => $make(Ids::BIRCH_SIGN, Ids::BIRCH_WALL_SIGN, fn() => VanillaItems::BIRCH_SIGN()),
WoodType::JUNGLE => $make(Ids::JUNGLE_SIGN, Ids::JUNGLE_WALL_SIGN, fn() => VanillaItems::JUNGLE_SIGN()),
WoodType::ACACIA => $make(Ids::ACACIA_SIGN, Ids::ACACIA_WALL_SIGN, fn() => VanillaItems::ACACIA_SIGN()),
WoodType::DARK_OAK => $make(Ids::DARK_OAK_SIGN, Ids::DARK_OAK_WALL_SIGN, fn() => VanillaItems::DARK_OAK_SIGN()),
WoodType::MANGROVE => $make(Ids::MANGROVE_SIGN, Ids::MANGROVE_WALL_SIGN, fn() => VanillaItems::MANGROVE_SIGN()),
WoodType::CRIMSON => $make(Ids::CRIMSON_SIGN, Ids::CRIMSON_WALL_SIGN, fn() => VanillaItems::CRIMSON_SIGN()),
WoodType::WARPED => $make(Ids::WARPED_SIGN, Ids::WARPED_WALL_SIGN, fn() => VanillaItems::WARPED_SIGN()),
WoodType::CHERRY => $make(Ids::CHERRY_SIGN, Ids::CHERRY_WALL_SIGN, fn() => VanillaItems::CHERRY_SIGN()),
};
}
public static function getTrapdoorIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_TRAPDOOR,
WoodType::SPRUCE => Ids::SPRUCE_TRAPDOOR,
WoodType::BIRCH => Ids::BIRCH_TRAPDOOR,
WoodType::JUNGLE => Ids::JUNGLE_TRAPDOOR,
WoodType::ACACIA => Ids::ACACIA_TRAPDOOR,
WoodType::DARK_OAK => Ids::DARK_OAK_TRAPDOOR,
WoodType::MANGROVE => Ids::MANGROVE_TRAPDOOR,
WoodType::CRIMSON => Ids::CRIMSON_TRAPDOOR,
WoodType::WARPED => Ids::WARPED_TRAPDOOR,
WoodType::CHERRY => Ids::CHERRY_TRAPDOOR,
});
}
public static function getButtonIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_BUTTON,
WoodType::SPRUCE => Ids::SPRUCE_BUTTON,
WoodType::BIRCH => Ids::BIRCH_BUTTON,
WoodType::JUNGLE => Ids::JUNGLE_BUTTON,
WoodType::ACACIA => Ids::ACACIA_BUTTON,
WoodType::DARK_OAK => Ids::DARK_OAK_BUTTON,
WoodType::MANGROVE => Ids::MANGROVE_BUTTON,
WoodType::CRIMSON => Ids::CRIMSON_BUTTON,
WoodType::WARPED => Ids::WARPED_BUTTON,
WoodType::CHERRY => Ids::CHERRY_BUTTON,
});
}
public static function getPressurePlateIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_PRESSURE_PLATE,
WoodType::SPRUCE => Ids::SPRUCE_PRESSURE_PLATE,
WoodType::BIRCH => Ids::BIRCH_PRESSURE_PLATE,
WoodType::JUNGLE => Ids::JUNGLE_PRESSURE_PLATE,
WoodType::ACACIA => Ids::ACACIA_PRESSURE_PLATE,
WoodType::DARK_OAK => Ids::DARK_OAK_PRESSURE_PLATE,
WoodType::MANGROVE => Ids::MANGROVE_PRESSURE_PLATE,
WoodType::CRIMSON => Ids::CRIMSON_PRESSURE_PLATE,
WoodType::WARPED => Ids::WARPED_PRESSURE_PLATE,
WoodType::CHERRY => Ids::CHERRY_PRESSURE_PLATE,
});
}
public static function getDoorIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_DOOR,
WoodType::SPRUCE => Ids::SPRUCE_DOOR,
WoodType::BIRCH => Ids::BIRCH_DOOR,
WoodType::JUNGLE => Ids::JUNGLE_DOOR,
WoodType::ACACIA => Ids::ACACIA_DOOR,
WoodType::DARK_OAK => Ids::DARK_OAK_DOOR,
WoodType::MANGROVE => Ids::MANGROVE_DOOR,
WoodType::CRIMSON => Ids::CRIMSON_DOOR,
WoodType::WARPED => Ids::WARPED_DOOR,
WoodType::CHERRY => Ids::CHERRY_DOOR,
});
}
public static function getFenceGateIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_FENCE_GATE,
WoodType::SPRUCE => Ids::SPRUCE_FENCE_GATE,
WoodType::BIRCH => Ids::BIRCH_FENCE_GATE,
WoodType::JUNGLE => Ids::JUNGLE_FENCE_GATE,
WoodType::ACACIA => Ids::ACACIA_FENCE_GATE,
WoodType::DARK_OAK => Ids::DARK_OAK_FENCE_GATE,
WoodType::MANGROVE => Ids::MANGROVE_FENCE_GATE,
WoodType::CRIMSON => Ids::CRIMSON_FENCE_GATE,
WoodType::WARPED => Ids::WARPED_FENCE_GATE,
WoodType::CHERRY => Ids::CHERRY_FENCE_GATE,
});
}
public static function getStairsIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_STAIRS,
WoodType::SPRUCE => Ids::SPRUCE_STAIRS,
WoodType::BIRCH => Ids::BIRCH_STAIRS,
WoodType::JUNGLE => Ids::JUNGLE_STAIRS,
WoodType::ACACIA => Ids::ACACIA_STAIRS,
WoodType::DARK_OAK => Ids::DARK_OAK_STAIRS,
WoodType::MANGROVE => Ids::MANGROVE_STAIRS,
WoodType::CRIMSON => Ids::CRIMSON_STAIRS,
WoodType::WARPED => Ids::WARPED_STAIRS,
WoodType::CHERRY => Ids::CHERRY_STAIRS,
});
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\world\Position;
class CampfireInventory extends SimpleInventory implements BlockInventory{
use BlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(4);
}
public function getMaxStackSize() : int{
return 1;
}
}

View File

@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
/**
* @var EnchantingOption[] $options
* @phpstan-var list<EnchantingOption>
*/
private array $options = [];
public function __construct(Position $holder){

143
src/block/tile/Campfire.php Normal file
View File

@ -0,0 +1,143 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\tile;
use pocketmine\block\Campfire as BlockCampfire;
use pocketmine\block\inventory\CampfireInventory;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\world\World;
class Campfire extends Spawnable implements Container{
use ContainerTrait;
private const TAG_FIRST_INPUT_ITEM = "Item1"; //TAG_Compound
private const TAG_SECOND_INPUT_ITEM = "Item2"; //TAG_Compound
private const TAG_THIRD_INPUT_ITEM = "Item3"; //TAG_Compound
private const TAG_FOURTH_INPUT_ITEM = "Item4"; //TAG_Compound
private const TAG_FIRST_COOKING_TIME = "ItemTime1"; //TAG_Int
private const TAG_SECOND_COOKING_TIME = "ItemTime2"; //TAG_Int
private const TAG_THIRD_COOKING_TIME = "ItemTime3"; //TAG_Int
private const TAG_FOURTH_COOKING_TIME = "ItemTime4"; //TAG_Int
protected CampfireInventory $inventory;
/** @var array<int, int> */
private array $cookingTimes = [];
public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
$this->inventory = new CampfireInventory($this->position);
$this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange(
static function(Inventory $unused) use ($world, $pos) : void{
$block = $world->getBlock($pos);
if($block instanceof BlockCampfire){
$world->setBlock($pos, $block);
}
})
);
}
public function getInventory() : CampfireInventory{
return $this->inventory;
}
public function getRealInventory() : CampfireInventory{
return $this->inventory;
}
/**
* @return int[]
* @phpstan-return array<int, int>
*/
public function getCookingTimes() : array{
return $this->cookingTimes;
}
/**
* @param int[] $cookingTimes
* @phpstan-param array<int, int> $cookingTimes
*/
public function setCookingTimes(array $cookingTimes) : void{
$this->cookingTimes = $cookingTimes;
}
public function readSaveData(CompoundTag $nbt) : void{
$items = [];
$listeners = $this->inventory->getListeners()->toArray();
$this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
foreach([
[0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
[1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME],
[2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME],
[3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
] as [$slot, $itemTag, $cookingTimeTag]){
if(($tag = $nbt->getTag($itemTag)) instanceof CompoundTag){
$items[$slot] = Item::nbtDeserialize($tag);
}
if(($tag = $nbt->getTag($cookingTimeTag)) instanceof IntTag){
$this->cookingTimes[$slot] = $tag->getValue();
}
}
$this->inventory->setContents($items);
$this->inventory->getListeners()->add(...$listeners);
}
protected function writeSaveData(CompoundTag $nbt) : void{
foreach([
[0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
[1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME],
[2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME],
[3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
] as [$slot, $itemTag, $cookingTimeTag]){
$item = $this->inventory->getItem($slot);
if(!$item->isNull()){
$nbt->setTag($itemTag, $item->nbtSerialize());
if(isset($this->cookingTimes[$slot])){
$nbt->setInt($cookingTimeTag, $this->cookingTimes[$slot]);
}
}
}
}
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
foreach([
0 => self::TAG_FIRST_INPUT_ITEM,
1 => self::TAG_SECOND_INPUT_ITEM,
2 => self::TAG_THIRD_INPUT_ITEM,
3 => self::TAG_FOURTH_INPUT_ITEM
] as $slot => $tag){
$item = $this->inventory->getItem($slot);
if(!$item->isNull()){
$nbt->setTag($tag, TypeConverter::getInstance()->getItemTranslator()->toNetworkNbt($item));
}
}
}
}

View File

@ -40,8 +40,12 @@ use function count;
class ChiseledBookshelf extends Tile implements Container{
use ContainerTrait;
private const TAG_LAST_INTERACTED_SLOT = "LastInteractedSlot"; //TAG_Int
private SimpleInventory $inventory;
private ?ChiseledBookshelfSlot $lastInteractedSlot = null;
public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
$this->inventory = new SimpleInventory(count(ChiseledBookshelfSlot::cases()));
@ -55,12 +59,30 @@ class ChiseledBookshelf extends Tile implements Container{
return $this->inventory;
}
public function readSaveData(CompoundTag $nbt) : void{
$this->loadItems($nbt);
public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{
return $this->lastInteractedSlot;
}
public function writeSaveData(CompoundTag $nbt) : void{
public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : void{
$this->lastInteractedSlot = $lastInteractedSlot;
}
public function readSaveData(CompoundTag $nbt) : void{
$this->loadItems($nbt);
$lastInteractedSlot = $nbt->getInt(self::TAG_LAST_INTERACTED_SLOT, 0);
if($lastInteractedSlot !== 0){
$this->lastInteractedSlot = ChiseledBookshelfSlot::tryFrom($lastInteractedSlot - 1);
}
}
protected function writeSaveData(CompoundTag $nbt) : void{
$this->saveItems($nbt);
$nbt->setInt(self::TAG_LAST_INTERACTED_SLOT, $this->lastInteractedSlot !== null ?
$this->lastInteractedSlot->value + 1 :
0
);
}
protected function loadItems(CompoundTag $tag) : void{

View File

@ -57,6 +57,7 @@ final class TileFactory{
$this->register(Bell::class, ["Bell", "minecraft:bell"]);
$this->register(BlastFurnace::class, ["BlastFurnace", "minecraft:blast_furnace"]);
$this->register(BrewingStand::class, ["BrewingStand", "minecraft:brewing_stand"]);
$this->register(Campfire::class, ["Campfire", "minecraft:campfire"]);
$this->register(Cauldron::class, ["Cauldron", "minecraft:cauldron"]);
$this->register(Chest::class, ["Chest", "minecraft:chest"]);
$this->register(ChiseledBookshelf::class, ["ChiseledBookshelf", "minecraft:chiseled_bookshelf"]);
@ -79,7 +80,6 @@ final class TileFactory{
$this->register(MobHead::class, ["Skull", "minecraft:skull"]);
$this->register(GlowingItemFrame::class, ["GlowItemFrame"]);
//TODO: Campfire
//TODO: ChalkboardBlock
//TODO: ChemistryTable
//TODO: CommandBlock

View File

@ -81,10 +81,12 @@ enum BannerPatternType{
case DIAGONAL_RIGHT;
case DIAGONAL_UP_LEFT;
case DIAGONAL_UP_RIGHT;
case FLOW;
case FLOWER;
case GLOBE;
case GRADIENT;
case GRADIENT_UP;
case GUSTER;
case HALF_HORIZONTAL;
case HALF_HORIZONTAL_BOTTOM;
case HALF_VERTICAL;

View File

@ -0,0 +1,38 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
/**
* Represents copper blocks that have oxidized and waxed variations.
*/
interface CopperMaterial{
public function getOxidation() : CopperOxidation;
public function setOxidation(CopperOxidation $oxidation) : CopperMaterial;
public function isWaxed() : bool;
public function setWaxed(bool $waxed) : CopperMaterial;
}

View File

@ -53,6 +53,7 @@ enum LeavesType{
case AZALEA;
case FLOWERING_AZALEA;
case CHERRY;
case PALE_OAK;
public function getDisplayName() : string{
return match($this){
@ -65,7 +66,8 @@ enum LeavesType{
self::MANGROVE => "Mangrove",
self::AZALEA => "Azalea",
self::FLOWERING_AZALEA => "Flowering Azalea",
self::CHERRY => "Cherry"
self::CHERRY => "Cherry",
self::PALE_OAK => "Pale Oak",
};
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\math\Facing;
/**
* Used by blocks that can have multiple target faces in the area of one solid block, such as covering three sides of a corner.
*/
trait MultiAnyFacingTrait{
/** @var int[] */
protected array $faces = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->facingFlags($this->faces);
}
/** @return int[] */
public function getFaces() : array{ return $this->faces; }
public function hasFace(int $face) : bool{
return isset($this->faces[$face]);
}
/**
* @param int[] $faces
* @return $this
*/
public function setFaces(array $faces) : self{
$uniqueFaces = [];
foreach($faces as $face){
Facing::validate($face);
$uniqueFaces[$face] = $face;
}
$this->faces = $uniqueFaces;
return $this;
}
/** @return $this */
public function setFace(int $face, bool $value) : self{
Facing::validate($face);
if($value){
$this->faces[$face] = $face;
}else{
unset($this->faces[$face]);
}
return $this;
}
}

View File

@ -0,0 +1,96 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\Block;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function array_key_first;
use function count;
/**
* Used by blocks that have multiple support requirements in the area of one solid block, such as covering three sides of a corner.
* Prevents placement if support isn't available and automatically destroys a block side if it's support is removed.
*/
trait MultiAnySupportTrait{
use MultiAnyFacingTrait;
/**
* Returns a list of faces that block should already have when placed.
*
* @return int[]
*/
abstract protected function getInitialPlaceFaces(Block $blockReplace) : array;
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->faces = $this->getInitialPlaceFaces($blockReplace);
$availableFaces = $this->getAvailableFaces();
if(count($availableFaces) === 0){
return false;
}
$opposite = Facing::opposite($face);
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
$this->faces[$placedFace] = $placedFace;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$changed = false;
foreach($this->faces as $face){
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
unset($this->faces[$face]);
$changed = true;
}
}
if($changed){
$world = $this->position->getWorld();
if(count($this->faces) === 0){
$world->useBreakOn($this->position);
}else{
$world->setBlock($this->position, $this);
}
}
}
/**
* @return array<int, int> $faces
*/
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
$faces[$face] = $face;
}
}
return $faces;
}
}

View File

@ -59,11 +59,15 @@ enum RecordType{
case DISK_CAT;
case DISK_BLOCKS;
case DISK_CHIRP;
case DISK_CREATOR;
case DISK_CREATOR_MUSIC_BOX;
case DISK_FAR;
case DISK_MALL;
case DISK_MELLOHI;
case DISK_OTHERSIDE;
case DISK_PIGSTEP;
case DISK_PRECIPICE;
case DISK_RELIC;
case DISK_STAL;
case DISK_STRAD;
case DISK_WARD;
@ -83,11 +87,15 @@ enum RecordType{
self::DISK_CAT => ["C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()],
self::DISK_BLOCKS => ["C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()],
self::DISK_CHIRP => ["C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()],
self::DISK_CREATOR => ["Lena Raine - Creator", LevelSoundEvent::RECORD_CREATOR, KnownTranslationFactory::item_record_creator_desc()],
self::DISK_CREATOR_MUSIC_BOX => ["Lena Raine - Creator (Music Box)", LevelSoundEvent::RECORD_CREATOR_MUSIC_BOX, KnownTranslationFactory::item_record_creator_music_box_desc()],
self::DISK_FAR => ["C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()],
self::DISK_MALL => ["C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()],
self::DISK_MELLOHI => ["C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()],
self::DISK_OTHERSIDE => ["Lena Raine - otherside", LevelSoundEvent::RECORD_OTHERSIDE, KnownTranslationFactory::item_record_otherside_desc()],
self::DISK_PIGSTEP => ["Lena Raine - Pigstep", LevelSoundEvent::RECORD_PIGSTEP, KnownTranslationFactory::item_record_pigstep_desc()],
self::DISK_PRECIPICE => ["Aaron Cherof - Precipice", LevelSoundEvent::RECORD_PRECIPICE, KnownTranslationFactory::item_record_precipice_desc()],
self::DISK_RELIC => ["Aaron Cherof - Relic", LevelSoundEvent::RECORD_RELIC, KnownTranslationFactory::item_record_relic_desc()],
self::DISK_STAL => ["C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()],
self::DISK_STRAD => ["C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()],
self::DISK_WARD => ["C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()],

View File

@ -36,13 +36,17 @@ use function str_contains;
class SignText{
public const LINE_COUNT = 4;
/** @var string[] */
/**
* @var string[]
* @phpstan-var array{0: string, 1: string, 2: string, 3: string}
*/
private array $lines;
private Color $baseColor;
private bool $glowing;
/**
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
* @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines
*
* @throws \InvalidArgumentException if the array size is greater than 4
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
@ -82,6 +86,7 @@ class SignText{
* Returns an array of lines currently on the sign.
*
* @return string[]
* @phpstan-return array{0: string, 1: string, 2: string, 3: string}
*/
public function getLines() : array{
return $this->lines;

View File

@ -53,6 +53,7 @@ enum WoodType{
case CRIMSON;
case WARPED;
case CHERRY;
case PALE_OAK;
public function getDisplayName() : string{
return match($this){
@ -66,6 +67,7 @@ enum WoodType{
self::CRIMSON => "Crimson",
self::WARPED => "Warped",
self::CHERRY => "Cherry",
self::PALE_OAK => "Pale Oak",
};
}

View File

@ -0,0 +1,60 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
/**
* @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list<string> $args) : mixed
*/
final class ClosureCommand extends Command{
/** @phpstan-var Execute */
private \Closure $execute;
/**
* @param string[] $permissions
* @phpstan-param Execute $execute
*/
public function __construct(
string $name,
\Closure $execute,
array $permissions,
Translatable|string $description = "",
Translatable|string|null $usageMessage = null,
array $aliases = []
){
Utils::validateCallableSignature(
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
$execute,
);
$this->execute = $execute;
parent::__construct($name, $description, $usageMessage, $aliases);
$this->setPermissions($permissions);
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
return ($this->execute)($sender, $this, $commandLabel, $args);
}
}

View File

@ -44,10 +44,16 @@ abstract class Command{
private string $nextLabel;
private string $label;
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $aliases = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $activeAliases = [];
private ?CommandMap $commandMap = null;
@ -62,6 +68,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
$this->name = $name;
@ -182,6 +189,7 @@ abstract class Command{
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getAliases() : array{
return $this->activeAliases;
@ -201,6 +209,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function setAliases(array $aliases) : void{
$this->aliases = $aliases;

View File

@ -64,12 +64,14 @@ use pocketmine\command\defaults\TransferServerCommand;
use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\defaults\XpCommand;
use pocketmine\command\utils\CommandStringHelper;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use function array_shift;
use function count;
use function implode;
@ -80,7 +82,10 @@ use function trim;
class SimpleCommandMap implements CommandMap{
/** @var Command[] */
/**
* @var Command[]
* @phpstan-var array<string, Command>
*/
protected array $knownCommands = [];
public function __construct(private Server $server){
@ -128,7 +133,8 @@ class SimpleCommandMap implements CommandMap{
new TitleCommand(),
new TransferServerCommand(),
new VersionCommand(),
new WhitelistCommand()
new WhitelistCommand(),
new XpCommand(),
]);
}
@ -169,7 +175,7 @@ class SimpleCommandMap implements CommandMap{
}
public function unregister(Command $command) : bool{
foreach($this->knownCommands as $lbl => $cmd){
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
if($cmd === $command){
unset($this->knownCommands[$lbl]);
}
@ -237,6 +243,7 @@ class SimpleCommandMap implements CommandMap{
/**
* @return Command[]
* @phpstan-return array<string, Command>
*/
public function getCommands() : array{
return $this->knownCommands;
@ -245,7 +252,7 @@ class SimpleCommandMap implements CommandMap{
public function registerServerAliases() : void{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
if(str_contains($alias, ":")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
continue;

View File

@ -26,28 +26,28 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\scheduler\BulkCurlTaskOperation;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\InternetException;
use pocketmine\utils\InternetRequestResult;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path;
use function count;
use function fclose;
use function file_exists;
use function fopen;
use function fseek;
use function fwrite;
use function http_build_query;
use function implode;
use function is_array;
use function json_decode;
use function mkdir;
use function stream_get_contents;
use function strtolower;
use const CURLOPT_AUTOREFERER;
use const CURLOPT_FOLLOWLOCATION;
@ -101,82 +101,91 @@ class TimingsCommand extends VanillaCommand{
TimingsHandler::reload();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
}elseif($mode === "merged" || $mode === "report" || $paste){
$timings = "";
if($paste){
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail");
}else{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}
$fileTimings = fopen($timings, "a+b");
}
$lines = TimingsHandler::printTimings();
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}
if($paste){
fseek($fileTimings, 0);
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings)
];
fclose($fileTimings);
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}else{
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
$timingsPromise = TimingsHandler::requestPrintTimings();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
$timingsPromise->onCompletion(
fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender),
fn() => throw new AssumptionFailedError("This promise is not expected to be rejected")
);
}else{
throw new InvalidCommandSyntaxException();
}
return true;
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function createReportFile(array $lines, CommandSender $sender) : void{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}
$fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function uploadReport(array $lines, CommandSender $sender) : void{
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => implode("\n", $lines)
];
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\entity\Attribute;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\TextFormat;
use function abs;
use function count;
use function str_ends_with;
use function substr;
class XpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"xp",
KnownTranslationFactory::pocketmine_command_xp_description(),
KnownTranslationFactory::pocketmine_command_xp_usage()
);
$this->setPermissions([
DefaultPermissionNames::COMMAND_XP_SELF,
DefaultPermissionNames::COMMAND_XP_OTHER
]);
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
if(count($args) < 1){
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
if($player === null){
return true;
}
$xpManager = $player->getXpManager();
if(str_ends_with($args[0], "L")){
$xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError();
$maxXpLevel = (int) $xpLevelAttr->getMaxValue();
$currentXpLevel = $xpManager->getXpLevel();
$xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel);
if($xpLevels >= 0){
$xpManager->addXpLevels($xpLevels, false);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName()));
}else{
$xpLevels = abs($xpLevels);
$xpManager->subtractXpLevels($xpLevels);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName()));
}
}else{
$xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX);
if($xp < 0){
$sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED));
}else{
$xpManager->addXp($xp, false);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName()));
}
}
return true;
}
}

View File

@ -29,23 +29,21 @@ use pocketmine\utils\Process;
use function cli_set_process_title;
use function count;
use function dirname;
use function feof;
use function fwrite;
use function stream_socket_client;
use function is_numeric;
use const PHP_EOL;
use const STDOUT;
if(count($argv) !== 2 || !is_numeric($argv[1])){
echo "Usage: " . $argv[0] . " <command token seed>" . PHP_EOL;
exit(1);
}
$commandTokenSeed = (int) $argv[1];
require dirname(__DIR__, 2) . '/vendor/autoload.php';
if(count($argv) !== 2){
die("Please provide a server to connect to");
}
@cli_set_process_title('PocketMine-MP Console Reader');
$errCode = null;
$errMessage = null;
$socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
if($socket === false){
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
}
/** @phpstan-var ThreadSafeArray<int, string> $channel */
$channel = new ThreadSafeArray();
@ -75,15 +73,15 @@ $thread = new class($channel) extends NativeThread{
};
$thread->start(NativeThread::INHERIT_NONE);
while(!feof($socket)){
while(true){
$line = $channel->synchronized(function() use ($channel) : ?string{
if(count($channel) === 0){
$channel->wait(1_000_000);
}
$line = $channel->shift();
return $line;
return $channel->shift();
});
if(@fwrite($socket, ($line ?? "") . "\n") === false){
$message = $line !== null ? ConsoleReaderChildProcessUtils::createMessage($line, $commandTokenSeed) : "";
if(@fwrite(STDOUT, $message . "\n") === false){
//Always send even if there's no line, to check if the parent is alive
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
//false even though the connection is actually broken. However, fwrite() will fail.

View File

@ -29,19 +29,16 @@ use Symfony\Component\Filesystem\Path;
use function base64_encode;
use function fgets;
use function fopen;
use function mt_rand;
use function preg_replace;
use function proc_close;
use function proc_open;
use function proc_terminate;
use function rtrim;
use function sprintf;
use function stream_select;
use function stream_socket_accept;
use function stream_socket_get_name;
use function stream_socket_server;
use function stream_socket_shutdown;
use function trim;
use const PHP_BINARY;
use const STREAM_SHUT_RDWR;
/**
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
@ -58,44 +55,44 @@ use const STREAM_SHUT_RDWR;
* communication.
*/
final class ConsoleReaderChildProcessDaemon{
public const TOKEN_DELIMITER = ":";
public const TOKEN_HASH_ALGO = "xxh3";
private \PrefixedLogger $logger;
/** @var resource */
private $subprocess;
/** @var resource */
private $socket;
private int $commandTokenSeed;
public function __construct(
\Logger $logger
){
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
$this->commandTokenSeed = mt_rand();
$this->prepareSubprocess();
}
private function prepareSubprocess() : void{
$server = stream_socket_server("tcp://127.0.0.1:0");
if($server === false){
throw new \RuntimeException("Failed to open console reader socket server");
}
$address = Utils::assumeNotFalse(stream_socket_get_name($server, false), "stream_socket_get_name() shouldn't return false here");
//Windows sucks, and likes to corrupt UTF-8 file paths when they travel to the subprocess, so we base64 encode
//the path to avoid the problem. This is an abysmally shitty hack, but here we are :(
$sub = Utils::assumeNotFalse(proc_open(
[PHP_BINARY, '-dopcache.enable_cli=0', '-r', sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))), $address],
[
PHP_BINARY,
'-dopcache.enable_cli=0',
'-r',
sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))),
(string) $this->commandTokenSeed
],
[
1 => ['socket'],
2 => fopen("php://stderr", "w"),
],
$pipes
), "Something has gone horribly wrong");
$client = stream_socket_accept($server, 15);
if($client === false){
throw new AssumptionFailedError("stream_socket_accept() returned false");
}
stream_socket_shutdown($server, STREAM_SHUT_RDWR);
$this->subprocess = $sub;
$this->socket = $client;
$this->socket = $pipes[1];
}
private function shutdownSubprocess() : void{
@ -104,7 +101,6 @@ final class ConsoleReaderChildProcessDaemon{
//the first place).
proc_terminate($this->subprocess);
proc_close($this->subprocess);
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
}
public function readLine() : ?string{
@ -112,13 +108,27 @@ final class ConsoleReaderChildProcessDaemon{
$w = null;
$e = null;
if(stream_select($r, $w, $e, 0, 0) === 1){
$command = fgets($this->socket);
if($command === false){
$line = fgets($this->socket);
if($line === false){
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
$this->shutdownSubprocess();
$this->prepareSubprocess();
return null;
}
$line = rtrim($line, "\n");
if($line === ""){
//keepalive
return null;
}
$command = ConsoleReaderChildProcessUtils::parseMessage($line, $this->commandTokenSeed);
if($command === null){
//this is not a command - it may be some kind of error output from the subprocess
//write it directly to the console
$this->logger->warning("Unexpected output from child process: $line");
return null;
}
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");

View File

@ -0,0 +1,71 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\console;
use function hash;
use function strlen;
use function strrpos;
use function substr;
final class ConsoleReaderChildProcessUtils{
public const TOKEN_DELIMITER = ":";
public const TOKEN_HASH_ALGO = "xxh3";
private function __construct(){
}
/**
* Creates an IPC message to transmit a user's input command to the parent process.
*
* Unfortunately we can't currently provide IPC pipes other than stdout/stderr to subprocesses on Windows, so this
* adds a hash of the user input (with a counter as salt) to prevent unintended process output (like error messages)
* from being treated as user input.
*/
public static function createMessage(string $line, int &$counter) : string{
$token = hash(self::TOKEN_HASH_ALGO, $line, options: ['seed' => $counter]);
$counter++;
return $line . self::TOKEN_DELIMITER . $token;
}
/**
* Extracts a command from an IPC message from the console reader subprocess.
* Returns the user's input command, or null if this isn't a user input.
*/
public static function parseMessage(string $message, int &$counter) : ?string{
$delimiterPos = strrpos($message, self::TOKEN_DELIMITER);
if($delimiterPos !== false){
$left = substr($message, 0, $delimiterPos);
$right = substr($message, $delimiterPos + strlen(self::TOKEN_DELIMITER));
$expectedToken = hash(self::TOKEN_HASH_ALGO, $left, options: ['seed' => $counter]);
if($expectedToken === $right){
$counter++;
return $left;
}
}
return null;
}
}

View File

@ -110,14 +110,15 @@ class CraftingManager{
/**
* @param Item[] $items
* @phpstan-param list<Item> $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
/** @var Item[] $result */
$result = [];
foreach($items as $i => $item){
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
@ -134,6 +135,7 @@ class CraftingManager{
/**
* @param Item[] $outputs
* @phpstan-param list<Item> $outputs
*/
private static function hashOutputs(array $outputs) : string{
$outputs = self::pack($outputs);

View File

@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{
*/
private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{
$result = [];
foreach($data as $i => $item){
foreach(Utils::promoteKeys($data) as $i => $item){
if(!is_object($item)){
throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item));
}
@ -275,7 +275,8 @@ final class CraftingManagerFromDataHelper{
"furnace" => FurnaceType::FURNACE,
"blast_furnace" => FurnaceType::BLAST_FURNACE,
"smoker" => FurnaceType::SMOKER,
//TODO: campfire
"campfire" => FurnaceType::CAMPFIRE,
"soul_campfire" => FurnaceType::SOUL_CAMPFIRE,
default => null
};
if($furnaceType === null){

View File

@ -30,6 +30,7 @@ interface CraftingRecipe{
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
*
* @return RecipeIngredient[]
* @phpstan-return list<RecipeIngredient>
*/
public function getIngredientList() : array;
@ -37,6 +38,7 @@ interface CraftingRecipe{
* Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed.
*
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array;

View File

@ -25,6 +25,7 @@ namespace pocketmine\crafting;
use pocketmine\utils\LegacyEnumShimTrait;
use pocketmine\world\sound\BlastFurnaceSound;
use pocketmine\world\sound\CampfireSound;
use pocketmine\world\sound\FurnaceSound;
use pocketmine\world\sound\SmokerSound;
use pocketmine\world\sound\Sound;
@ -35,8 +36,10 @@ use function spl_object_id;
* These are retained for backwards compatibility only.
*
* @method static FurnaceType BLAST_FURNACE()
* @method static FurnaceType CAMPFIRE()
* @method static FurnaceType FURNACE()
* @method static FurnaceType SMOKER()
* @method static FurnaceType SOUL_CAMPFIRE()
*
* @phpstan-type TMetadata array{0: int, 1: Sound}
*/
@ -46,6 +49,8 @@ enum FurnaceType{
case FURNACE;
case BLAST_FURNACE;
case SMOKER;
case CAMPFIRE;
case SOUL_CAMPFIRE;
/**
* @phpstan-return TMetadata
@ -58,6 +63,7 @@ enum FurnaceType{
self::FURNACE => [200, new FurnaceSound()],
self::BLAST_FURNACE => [100, new BlastFurnaceSound()],
self::SMOKER => [100, new SmokerSound()],
self::CAMPFIRE, self::SOUL_CAMPFIRE => [600, new CampfireSound()]
};
}

View File

@ -32,11 +32,20 @@ use function str_contains;
use function strlen;
class ShapedRecipe implements CraftingRecipe{
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $shape = [];
/** @var RecipeIngredient[] char => RecipeIngredient map */
/**
* @var RecipeIngredient[] char => RecipeIngredient map
* @phpstan-var array<string, RecipeIngredient>
*/
private array $ingredientList = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results = [];
private int $height;
@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*
* @phpstan-param list<string> $shape
* @phpstan-param array<string, RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $shape, array $ingredients, array $results){
$this->height = count($shape);
@ -84,7 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
foreach($ingredients as $char => $i){
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
}
@ -105,6 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -112,6 +126,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array{
return $this->getResults();
@ -119,6 +134,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return (RecipeIngredient|null)[][]
* @phpstan-return list<list<RecipeIngredient|null>>
*/
public function getIngredientMap() : array{
$ingredients = [];
@ -132,9 +148,6 @@ class ShapedRecipe implements CraftingRecipe{
return $ingredients;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
$ingredients = [];
@ -157,6 +170,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* Returns an array of strings containing characters representing the recipe's shape.
* @return string[]
* @phpstan-return list<string>
*/
public function getShape() : array{
return $this->shape;

View File

@ -28,15 +28,24 @@ use pocketmine\utils\Utils;
use function count;
class ShapelessRecipe implements CraftingRecipe{
/** @var RecipeIngredient[] */
/**
* @var RecipeIngredient[]
* @phpstan-var list<RecipeIngredient>
*/
private array $ingredients = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results;
private ShapelessRecipeType $type;
/**
* @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
* @param Item[] $results List of result items created by this recipe.
*
* @phpstan-param list<RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){
$this->type = $type;
@ -50,6 +59,7 @@ class ShapelessRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -63,9 +73,6 @@ class ShapelessRecipe implements CraftingRecipe{
return $this->type;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
return $this->ingredients;
}

View File

@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{
public string $plugin = "";
/** @var string[] */
/**
* @var string[]
* @phpstan-var array<int, string>
*/
public array $code = [];
/** @var string[] */
@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{
*/
public array $plugins = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
public array $parameters = [];
public string $serverDotProperties = "";

View File

@ -56,9 +56,11 @@ final class BannerPatternTypeIdMap{
BannerPatternType::DIAGONAL_UP_LEFT => "ld",
BannerPatternType::DIAGONAL_UP_RIGHT => "rud",
BannerPatternType::FLOWER => "flo",
BannerPatternType::FLOW => "flw",
BannerPatternType::GLOBE => "glb",
BannerPatternType::GRADIENT => "gra",
BannerPatternType::GRADIENT_UP => "gru",
BannerPatternType::GUSTER => "gus",
BannerPatternType::HALF_HORIZONTAL => "hh",
BannerPatternType::HALF_HORIZONTAL_BOTTOM => "hhb",
BannerPatternType::HALF_VERTICAL => "vh",

View File

@ -35,6 +35,7 @@ final class BedrockDataFiles{
public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt';
public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json';
public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json';
public const BLOCK_PROPERTIES_TABLE_JSON = BEDROCK_DATA_PATH . '/block_properties_table.json';
public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json';
public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt';
public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json';
@ -44,6 +45,7 @@ final class BedrockDataFiles{
public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json';
public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json';
public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json';
public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json';
public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin';
public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json';
public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';

View File

@ -122,4 +122,5 @@ final class BiomeIds{
public const DEEP_DARK = 190;
public const MANGROVE_SWAMP = 191;
public const CHERRY_GROVE = 192;
public const PALE_GARDEN = 193;
}

View File

@ -43,6 +43,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::PROJECTILE_PROTECTION, VanillaEnchantments::PROJECTILE_PROTECTION());
$this->register(EnchantmentIds::THORNS, VanillaEnchantments::THORNS());
$this->register(EnchantmentIds::RESPIRATION, VanillaEnchantments::RESPIRATION());
$this->register(EnchantmentIds::AQUA_AFFINITY, VanillaEnchantments::AQUA_AFFINITY());
$this->register(EnchantmentIds::SHARPNESS, VanillaEnchantments::SHARPNESS());
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
@ -65,5 +66,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\item\GoatHornType;
use pocketmine\utils\SingletonTrait;
final class GoatHornTypeIdMap{
use SingletonTrait;
/** @phpstan-use IntSaveIdMapTrait<GoatHornType> */
use IntSaveIdMapTrait;
private function __construct(){
foreach(GoatHornType::cases() as $case){
$this->register(match($case){
GoatHornType::PONDER => GoatHornTypeIds::PONDER,
GoatHornType::SING => GoatHornTypeIds::SING,
GoatHornType::SEEK => GoatHornTypeIds::SEEK,
GoatHornType::FEEL => GoatHornTypeIds::FEEL,
GoatHornType::ADMIRE => GoatHornTypeIds::ADMIRE,
GoatHornType::CALL => GoatHornTypeIds::CALL,
GoatHornType::YEARN => GoatHornTypeIds::YEARN,
GoatHornType::DREAM => GoatHornTypeIds::DREAM
}, $case);
}
}
}

Some files were not shown because too many files have changed in this diff Show More