Compare commits

...

152 Commits
3.3.3 ... 3.5.1

Author SHA1 Message Date
b42132a7c3 Release 3.5.1 2018-12-14 09:45:47 +00:00
c05697f506 Merge branch 'release/3.4' into release/3.5 2018-12-14 09:39:21 +00:00
d4fe1b8ece 3.4.3 is next 2018-12-14 09:30:44 +00:00
9b2653fb6f Release 3.4.2 2018-12-14 09:30:14 +00:00
ed88684e71 Fixed invisible FloatingTextParticle crashing the server, closes #2560 2018-12-14 09:30:14 +00:00
cbb9c4f298 Entity: require scale > 0 in setScale(), fixes #2563 2018-12-14 09:30:14 +00:00
660d42e8d1 Backport usage of SetLocalPlayerAsInitializedPacket to 3.4 (#2558)
This fixes various problems, such as forms not working on PlayerJoinEvent.
2018-12-13 20:07:17 +00:00
dbeceb02f9 fixup 1.8 crafting, take 2 2018-12-13 10:54:15 +00:00
fd77dd0066 Revert "Fixed crafting grid transaction handling, close #2559"
This reverts commit dfeb62491a.
2018-12-13 10:38:04 +00:00
87ce87112b Merge branch 'release/3.4' into release/3.5 2018-12-13 09:56:21 +00:00
1d71f5edb3 DataPacket: more detail in error messages for undefined fields 2018-12-13 09:55:50 +00:00
0f620157e8 3.5.1 is next 2018-12-12 19:20:40 +00:00
2323601f98 Release 3.5.0 2018-12-12 19:03:07 +00:00
d34b94302f fixed lava fizz sound 2018-12-12 18:00:43 +00:00
ec4c61e113 fix extradata defaults for broadcastLevelSoundEvent
fixes TNT sounds not working, amongst other things
2018-12-12 17:42:52 +00:00
231e491bb9 Fixed black spawn eggs 2018-12-12 17:14:13 +00:00
69cdc6f13a Remove misleading default value for NetworkInventoryAction windowId 2018-12-12 16:08:47 +00:00
dfeb62491a Fixed crafting grid transaction handling, close #2559 2018-12-12 15:41:54 +00:00
178eedb536 Merge branch 'release/3.4' into release/3.5 2018-12-12 10:12:12 +00:00
4975da2aae NetworkInventoryAction: additional validity checks 2018-12-12 10:11:44 +00:00
5946ec8819 fix inventory bug, silence debug spam, shut the fuck up MCPE 2018-12-11 21:57:07 +00:00
abf0dee426 bump version 2018-12-11 21:07:56 +00:00
30f5a8fac6 Protocol changes for 1.8.0 release 2018-12-11 21:05:03 +00:00
f704061618 Tree: fixed being able to overwrite other trees
this was observable by planting a sapling underneath an existing tree and punching it with bone meal.

This change will also prevent trees generating too close together.
2018-12-09 19:26:48 +00:00
23dc6e09d8 Sync DevTools submodule 2018-12-09 15:34:06 +00:00
15b7fc978e 3.4.2 is next 2018-12-08 17:00:36 +00:00
bb396174ba Release 3.4.1 2018-12-08 17:00:07 +00:00
dcef3cba21 CrashDump: cleanup some version related stuff
this should have been done a long time ago, but we didn't want to cause compatibility problems with CA. Now it enforces version checks, this isn't a problem anymore.
2018-12-08 16:58:06 +00:00
5f8a9f8747 Add a new format_version field to crashdumps
this will be used in the future to allow CA to decide how to decode crashdumps and/or refuse crashdumps from incompatible versions.
2018-12-08 16:57:57 +00:00
84e41e6967 3.4.1 is next 2018-12-06 21:01:57 +00:00
5e0e0daf7d Release 3.4.0 2018-12-06 20:45:57 +00:00
a95694ed06 Add signature validation for some user-defined callbacks 2018-12-04 18:33:58 +00:00
762405d16a Add daverandom/callback-validator as a dependency 2018-12-04 17:14:37 +00:00
e3f46987f5 Liquid: Add events to allow controlling flow and fusion (#2547) 2018-12-04 13:14:22 +00:00
e4223bb7dc Level: Duct tape fix for crashy trees at the top of the world
this doesn't fix shit but it at least doesn't crash. Fixing this properly can't be effectively done any other way without backwards compatibility breaks. Fortunately it's not common practice to grow trees at the top of the world.
2018-12-03 18:30:27 +00:00
f091446ec7 Sync NBT dependency 2018-12-03 16:15:02 +00:00
b0f891081c Mark EXHAUSTION as non-syncable
this attribute is not visible on the client and is only used for controlling saturation depletion. It's extremely spammy and as such really shouldn't be sent over network. This has also been causing some minor client-side performance issues in survival.
2018-12-02 16:43:00 +00:00
acd7c9b336 Permission: Throw exception on unknown values in getByName()
Previously, writing wrong values into plugin.yml for permission defaults would cause the permission to be silently denied to everyone.
2018-12-01 15:56:44 +00:00
75482124f2 Merge branch 'release/3.3' into release/3.4 2018-12-01 10:09:46 +00:00
288599cbe7 3.3.5 is next 2018-12-01 10:00:59 +00:00
aa7206126a Release 3.3.4 2018-12-01 10:00:38 +00:00
1a6db1c7ce DataPacket: add missing field
this must have been missing for how many years now? thanks @shoghicp

this is why we don't do releases on friday night... in my defence my device had the beta installed...
2018-12-01 09:29:53 +00:00
36c0c350a7 Merge branch 'release/3.3' into release/3.4 2018-11-30 18:37:28 +00:00
6295ef8a81 Add language option to server.properties (#2531)
This allows to save the language without rewriting pocketmine.yml. Since this is a "standard" config option (something that the user might want to directly modify) it's reasonable to put it in server.properties. pocketmine.yml is generally reserved for more advanced configuration options.
2018-11-30 13:25:04 +00:00
05dba61a69 Merge branch 'release/3.3' into release/3.4 2018-11-29 19:47:28 +00:00
c010ef45ed Merge branch 'release/3.3' into release/3.4 2018-11-29 18:46:00 +00:00
08ec021f78 Merge branch 'release/3.3' into release/3.4 2018-11-26 14:02:32 +00:00
b0060caaf7 Config: don't catch-all in save() 2018-11-25 16:35:59 +00:00
c90d1faa81 Merge remote-tracking branch 'origin/release/3.3' into release/3.4 2018-11-25 14:35:45 +00:00
449dda83fb Merge branch 'release/3.3' into release/3.4 2018-11-22 16:48:57 +00:00
cdf7e28251 shut up PhpStorm 2018-11-17 18:12:48 +00:00
a02f422d85 SubChunk: Fixed constant redefinition on worker threads when autoloading
this happens when workers inherit constants but not classes.
2018-11-17 16:29:53 +00:00
f8bfbc107d Reduce chunk memory usage by 20-60% by exploiting PHP copy-on-write behaviour (#2527)
This takes advantage of two key behaviours of PHP:
1. Assigning a string does not copy the string
2. Changing an offset in a string causes the string to be copied.

These two factors combined, along with the fact that blocklight and skylight arrays are usually all-zeros, allow us to produce a significant memory usage reduction of loaded chunks.
A freshly generated PM world with 3,332 chunks loaded drops from 310MB to 200MB memory usage with these changes applied.
2018-11-17 14:46:05 +00:00
554c029fbd Merge branch 'release/3.3' into release/3.4 2018-11-13 18:24:08 +00:00
de50f02076 Merge branch 'release/3.3' into release/3.4 2018-11-12 22:07:22 +00:00
46d9475568 Use Utils::getNiceClosureName() in PluginManager 2018-11-11 19:50:07 +00:00
788b278fc3 Utils: fixed handling of non-anonymous closure functions 2018-11-11 19:43:00 +00:00
2e4143f57e Merge branch 'release/3.3' into release/3.4 2018-11-11 12:38:53 +00:00
5284ad0346 Merge branch 'release/3.3' into release/3.4 2018-11-11 11:15:46 +00:00
7cf36f460b Merge branch 'release/3.3' into release/3.4 2018-11-10 22:37:08 +00:00
a5f776af2f Merge branch 'release/3.3' into release/3.4 2018-11-07 22:11:13 +00:00
3d2701e775 Merge branch 'release/3.3' into release/3.4 2018-11-04 23:32:56 +00:00
e26af3fa1b TaskScheduler: don't catch unexpected exceptions
this means that errors in scheduled tasks which are uncaught will now cause a server crash.
2018-11-04 23:22:30 +00:00
1634dd62e3 Don't catch unexpected exceptions during command execution 2018-11-04 23:11:51 +00:00
755db3dac8 Added a ClosureTask implementation for easier task scheduling (#2497) 2018-11-04 22:55:40 +00:00
3dabf90b0e Merge branch 'release/3.3' into release/3.4 2018-11-04 22:38:58 +00:00
0543c17849 Merge branch 'release/3.3' into release/3.4 2018-11-04 22:15:46 +00:00
87b471ce0f AsyncPool: reverse e0d5c79848, don't catch unexpected exceptions thrown by onCompletion()
this should never throw an uncaught exception, and if it does it indicates broken code.
2018-11-04 22:09:30 +00:00
055ba6aa7c Merge branch 'release/3.3' into release/3.4 2018-11-04 11:57:35 +00:00
af1227f154 Merge branch 'release/3.3' into release/3.4 2018-11-03 19:43:54 +00:00
9caf62778c AsyncTask: remove $serialize parameter from setResult()
Whether serialization is necessary can be determined automatically based on the type of variable.
2018-11-03 16:56:24 +00:00
d257d36e55 Merge branch 'release/3.3' into release/3.4 2018-11-03 15:14:27 +00:00
44d8a5528e Merge branch 'release/3.3' into release/3.4 2018-11-03 12:12:42 +00:00
265b61b3e6 Merge branch 'release/3.3' into release/3.4 2018-10-31 18:55:26 +00:00
cf43f479df Server: cleanup setting up of console 2018-10-30 16:59:03 +00:00
c143834632 Merge branch 'release/3.3' into release/3.4 2018-10-30 15:43:06 +00:00
a0eb6e23e5 Merge branch 'release/3.3' into release/3.4 2018-10-29 12:42:17 +00:00
0aa30295af Merge branch 'release/3.3' into release/3.4 2018-10-26 20:09:02 +01:00
c6a4bc4bf7 Merge branch 'release/3.3' into release/3.4 2018-10-25 19:36:44 +01:00
a714612453 Merge branch 'release/3.3' into release/3.4 2018-10-25 18:42:09 +01:00
f61e099828 Merge branch 'release/3.3' into release/3.4 2018-10-24 15:52:04 +01:00
447b9562bb Merge branch 'release/3.3' into release/3.4 2018-10-24 12:16:46 +01:00
cac21c2caf SubChunk: implement branchless read/write for nibble arrays (#2489)
this was inspired by https://hub.spigotmc.org/stash/projects/SPIGOT/repos/spigot/browse/CraftBukkit-Patches/0121-Branchless-NibbleArray.patch
2018-10-22 17:46:14 +01:00
6dd2597934 Merge branch 'release/3.3' into release/3.4 2018-10-21 18:17:07 +01:00
b35759cc25 Add /unban and /unban-ip as aliases of /pardon and /pardon-ip 2018-10-21 15:32:38 +01:00
2a40c0d82c Make use of isInLoadedTerrain() 2018-10-20 19:13:34 +01:00
8ac1b18b17 Level: add API method isInLoadedTerrain() 2018-10-20 19:09:53 +01:00
4aef9919dc Use newly added API method 2018-10-20 16:26:10 +01:00
43426a4c5c Level: Add API method getViewersForPosition()
This returns all players who have the given position within their view radius.
2018-10-20 16:25:56 +01:00
3028832cd3 Entity: remove redundant check from spawnTo()
this won't be reached if the player isn't using this chunk anyway.
2018-10-20 16:24:46 +01:00
9f8a2dc61a Make use of new API method getChunkAtPosition() 2018-10-20 15:58:29 +01:00
d9ebe6f321 Level: Added API method getChunkAtPosition()
This returns the chunk containing the given vector.
2018-10-20 15:54:13 +01:00
cb1eb1ee09 Level: Rename addGlobalPacket() to broadcastGlobalPacket()
this name makes the intention more clear and consistent with other functions.
2018-10-20 15:24:33 +01:00
d563b9e31b Level: Added API method broadcastPacketToViewers()
This supersedes addChunkPacket() in most cases, and has a more clear name. It broadcasts the given packet to every player who has the target position within their chunk load radius.
2018-10-20 15:14:41 +01:00
7c44eea625 Merge branch 'release/3.3' into release/3.4 2018-10-19 18:53:18 +01:00
646c8970b8 Merge branch 'release/3.3' into release/3.4 2018-10-19 15:56:01 +01:00
f1cd6940f9 Merge branch 'release/3.3' into release/3.4 2018-10-16 22:56:46 +01:00
4221e274d6 Merge branch 'release/3.3' into release/3.4 2018-10-16 18:20:14 +01:00
cd506bb443 shuffle back to 3.4 to make space for new MCPE release 2018-10-16 17:11:46 +01:00
4c8ffce86f Merge branch 'release/3.2' into release/3.3 2018-10-16 16:46:39 +01:00
df6bb2ea0e Merge branch 'release/3.2' into release/3.3 2018-10-16 09:51:19 +01:00
ba68192206 Fixed bad event handlers (whose errors get caught) breaking recursion protection for future event calls
This was observed in a recent crashdump where a plugin triggered a recursion error, but the stack trace did not contain any sign of a recursive event call. I conclude that this must have been caused by previous event handlers triggering errors 50 times in order to make the recursion detection break, because the recursion detection did not decrement the counter in cases where an exception was thrown.
2018-10-14 11:07:16 +01:00
6579930638 Revamp MetadataStore API (#2477)
This would be a lot less messy if we had generics, but no tango.
2018-10-12 12:16:21 +01:00
a0ab996b9f Merge branch 'release/3.2' into release/3.3 2018-10-12 09:31:48 +01:00
b261129788 Merge branch 'release/3.2' into release/3.3 2018-10-11 19:42:18 +01:00
4f2f373a24 Merge branch 'release/3.2' into release/3.3 2018-10-10 13:41:51 +01:00
de6d62aba2 Merge branch 'release/3.2' into release/3.3 2018-10-09 22:51:40 +01:00
6f694b0801 Merge branch 'release/3.2' into release/3.3 2018-10-07 19:45:26 +01:00
a3552875cb Merge branch 'release/3.2' into release/3.3 2018-10-07 17:48:26 +01:00
ab5aec6c30 Event: Remove unnecessary check from call() hot path
This check is completely unnecessary since handlers get unregistered when a plugin is disabled. Additionally, this is an extremely hot path and this change produces a modest 5% performance improvement to event calls.
2018-10-07 16:36:30 +01:00
0e508876d2 RakLibInterface: Disconnect players who trigger errors during handler
this is cleaner than leaving the player hanging for 5 seconds (which they'll often timeout from anyway). Banning the IP without kicking the player can often look like "lag" and end up getting brushed off as a performance issue.
2018-10-07 15:32:23 +01:00
50b89c30f8 Merge branch 'release/3.2' into release/3.3 2018-10-06 14:45:12 +01:00
495fdbd19f Move block and network namespaces away from PluginManager->callEvent()
the original step that wasn't supposed to cause conflicts, caused messy conflicts... so I might as well do this part too
2018-10-05 18:22:49 +01:00
620784e4e7 Merge branch 'release/3.2' into release/3.3 2018-10-05 17:44:03 +01:00
1dd6591ac1 Migrate a bunch of PluginManager->callEvent() usages to Event->call
This has the triple bonus effect of a) making a lot of code easier to read, b) reducing Server::getInstance() usages, and c) removing a whole bunch of Server dependencies.

The network and block namespaces are untouched by this commit due to potential for merge conflicts. These should be dealt with separately on master.
2018-10-05 17:30:06 +01:00
6efef3bbc7 Move event calling functionality to Event->call() method
This is dependent on the changes made in b1e0f82cbf. This now makes it possible to call events without fetching a Server reference, allowing to eliminate a vast array of Server dependencies.
2018-10-05 16:55:37 +01:00
b1e0f82cbf PluginManager: Stop catching exceptions thrown by event handlers (#2472)
The basic principle here is "if you're not expecting it, don't catch it".

Event handlers are **never** supposed to throw exceptions. If they do throw exceptions, it's usually going to one of two things;
1. Broken code producing an error
2. Code triggering (and not catching) a runtime error

Both 1) and 2) boil down to defective code on the part of the event handler, and thus should not be caught by the caller, but instead allowed to crash the server and produce a crashdump.

It's also undesirable to catch unexpected errors here for a few other reasons
- It leaves the owner of the event handler in an unknown, potentially unstable state
- It allows broken code to cause event handlers to spam the logger in events that happen frequently (for example movement handlers)
- It allows the process to continue down a train of further undefined behaviour, which may lead to more errors or ultimately a crash, so it makes no sense to hold off the inevitable.

This has a few advantages that are not merely inverted disadvantages:
- Crash dumps will now be created and automatically submitted for defective event handlers, allowing quicker issue location, debugging and fixing in plugins without manual user interaction
- Event calling now isn't dependent on Server to work.
2018-10-05 16:46:10 +01:00
c065cfbeda Merge branch 'release/3.2' into release/3.3 2018-10-04 16:41:03 +01:00
0171095036 Merge branch 'release/3.2' into release/3.3 2018-09-29 15:39:34 +01:00
9d8898a4ed Server: added API method hasOfflinePlayerData() 2018-09-27 16:36:42 +01:00
3bb22f9778 Merge branch 'release/3.2' into release/3.3 2018-09-26 13:12:34 +01:00
16c636df83 Merge branch 'release/3.2' into release/3.3 2018-09-24 18:27:04 -04:00
e597067a92 Merge branch 'release/3.3' of https://github.com/pmmp/pocketmine-mp into release/3.3 2018-09-23 16:43:36 +01:00
06f00020cd Merge branch 'release/3.2' into release/3.3 2018-09-23 16:35:20 +01:00
5eeaeb6c3e Level: Bail on trying to unload a level during level tick (#2435) 2018-09-22 13:40:50 +01:00
2712287995 Merge branch 'release/3.2' into release/3.3 2018-09-20 19:02:39 +01:00
6be5e75263 Merge branch 'release/3.2' into release/3.3 2018-09-20 17:04:55 +01:00
6b44f99dfb Merge branch 'release/3.2' into release/3.3 2018-09-20 16:50:04 +01:00
fa9ea6a7d7 Merge branch 'release/3.2' into release/3.3 2018-09-20 10:04:19 +01:00
5e94d20d79 Merge branch 'release/3.2' into release/3.3 2018-09-19 16:17:00 +01:00
ad9df6764d Merge branch 'release/3.2' into release/3.3 2018-09-18 12:32:07 +01:00
6309a242dc Merge branch 'release/3.2' into release/3.3 2018-09-18 12:22:26 +01:00
e58d015f14 Merge branch 'release/3.2' into release/3.3 2018-09-16 17:47:08 +01:00
0d65f9c4b8 Merge branch 'release/3.2' into release/3.3 2018-09-14 17:09:51 +01:00
22077c1fdd Merge branch 'release/3.2' into release/3.3 2018-09-14 16:18:19 +01:00
f33c19e77a Merge branch 'release/3.2' into release/3.3 2018-09-14 11:06:24 +01:00
09dea035d4 Level: Batch light updates at the end of the tick to amortize CPU cost (#2429)
this produces a 5x performance improvement for lighting updates during water flow, and 25% improvement for lava flow.
2018-09-12 10:33:28 +01:00
a9fc67663c Strip anti flight out of the core (#2428)
This may later be developed into a plugin if it is a desired feature, but having it in the core is unnecessary and a pain in the ass.
2018-09-12 10:06:57 +01:00
519659fd2b Merge branch 'release/3.2' into release/3.3 2018-09-11 19:48:23 +01:00
5134c0cf5a Merge branch 'release/3.2' into release/3.3 2018-09-11 12:28:09 +01:00
0aa63d269a Merge branch 'release/3.2' into release/3.3 2018-09-11 11:35:46 +01:00
191f0038b8 LightUpdate: Allow a single position to be set more than once before executing
This is needed for batched lighting updates to work. It also reduces the overhead involved with simply preparing a lighting update and moves the pain to the execute() instead.
2018-09-11 11:33:57 +01:00
99d6aa92cb Implemented rail connectivity (#2414) 2018-09-10 19:32:11 +01:00
90d01f5ed2 Merge branch 'release/3.2' into release/3.3 2018-09-08 14:25:20 +01:00
b70905b287 Merge branch 'release/3.2' into release/3.3 2018-09-06 19:31:05 +01:00
7a48c0b23d Merge branch 'release/3.2' into release/3.3 2018-09-06 19:15:41 +01:00
557fd34754 Make MainLogger independent of runtime-defined INI entries
Previously every thread using the logger had to inherit runtime-defined INI entries in order for the timezone to be set correctly. This removes that requirement.
2018-09-04 15:57:45 +01:00
32077d96b4 Merge branch 'release/3.2' into release/3.3 2018-09-04 11:56:58 +01:00
e621cde8f1 Player: Tighten validity checks for addWindow() (#2419)
- Don't allow the same window ID to be used when another window is already using it
- Detect window ID collisions when selecting IDs for regular containers (should never happen, but anything is possible)
2018-09-01 15:25:46 +01:00
2738e38aee Merge branch 'release/3.2' into release/3.3 2018-08-31 16:19:52 +01:00
72d447276b Merge branch 'release/3.2' into release/3.3 2018-08-30 15:46:54 +01:00
d0aff2ecbd Bump version for 3.3 dev branch 2018-08-30 11:05:58 +01:00
104 changed files with 1792 additions and 724 deletions

View File

@ -29,7 +29,8 @@
"pocketmine/binaryutils": "^0.1.0",
"pocketmine/nbt": "^0.2.1",
"pocketmine/math": "^0.2.0",
"pocketmine/snooze": "^0.1.0"
"pocketmine/snooze": "^0.1.0",
"daverandom/callback-validator": "dev-master"
},
"autoload": {
"psr-4": {

58
composer.lock generated
View File

@ -4,8 +4,48 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3536995c56bfc3dbd6ccc0994e88a636",
"content-hash": "2d120a3dd7d68958809c3662d1cb7c99",
"packages": [
{
"name": "daverandom/callback-validator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/DaveRandom/CallbackValidator.git",
"reference": "d87a08cddbc6099816ed01e50ce25cdfc43b542f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DaveRandom/CallbackValidator/zipball/d87a08cddbc6099816ed01e50ce25cdfc43b542f",
"reference": "d87a08cddbc6099816ed01e50ce25cdfc43b542f",
"shasum": ""
},
"require": {
"ext-reflection": "*",
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"DaveRandom\\CallbackValidator\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Wright",
"email": "cw@daverandom.com"
}
],
"description": "Tools for validating callback signatures",
"time": "2017-04-03T15:22:41+00:00"
},
{
"name": "pocketmine/binaryutils",
"version": "0.1.1",
@ -76,19 +116,20 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.2",
"version": "0.2.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b"
"reference": "291bf5cc2a94500eada1edbda51d15bed25a1e1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/474f0cf0a47656d0122b4f3f71302e694ed6977b",
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/291bf5cc2a94500eada1edbda51d15bed25a1e1c",
"reference": "291bf5cc2a94500eada1edbda51d15bed25a1e1c",
"shasum": ""
},
"require": {
"ext-zlib": "*",
"php": ">=7.2.0",
"php-64bit": "*",
"pocketmine/binaryutils": "^0.1.0"
@ -109,10 +150,10 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.2",
"source": "https://github.com/pmmp/NBT/tree/0.2.3",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2018-10-12T08:26:44+00:00"
"time": "2018-12-03T16:08:28+00:00"
},
{
"name": "pocketmine/raklib",
@ -226,7 +267,8 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"ext-pthreads": 20
"ext-pthreads": 20,
"daverandom/callback-validator": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View File

@ -33,6 +33,14 @@ use raklib\RakLib;
class CrashDump{
/**
* Crashdump data format version, used by the crash archive to decide how to decode the crashdump
* This should be incremented when backwards incompatible changes are introduced, such as fields being removed or
* having their content changed, version format changing, etc.
* It is not necessary to increase this when adding new fields.
*/
private const FORMAT_VERSION = 1;
/** @var Server */
private $server;
private $fp;
@ -54,6 +62,7 @@ class CrashDump{
if(!is_resource($this->fp)){
throw new \RuntimeException("Could not create Crash Dump");
}
$this->data["format_version"] = self::FORMAT_VERSION;
$this->data["time"] = $this->time;
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", $this->time));
$this->addLine();
@ -232,10 +241,10 @@ class CrashDump{
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["version"] = $version->getFullVersion(false);
$this->data["general"]["build"] = $version->getBuild();
$this->data["general"]["base_version"] = \pocketmine\BASE_VERSION;
$this->data["general"]["build"] = \pocketmine\BUILD_NUMBER;
$this->data["general"]["is_dev"] = \pocketmine\IS_DEVELOPMENT_BUILD;
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
$this->data["general"]["api"] = \pocketmine\BASE_VERSION;
$this->data["general"]["git"] = \pocketmine\GIT_COMMIT;
$this->data["general"]["raklib"] = RakLib::VERSION;
$this->data["general"]["uname"] = php_uname("a");

View File

@ -185,7 +185,7 @@ class MemoryManager{
}
$ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
$cycles = 0;
if($this->garbageCollectionTrigger){

View File

@ -35,7 +35,7 @@ class OfflinePlayer implements IPlayer, Metadatable{
/** @var Server */
private $server;
/** @var CompoundTag|null */
private $namedtag;
private $namedtag = null;
/**
* @param Server $server
@ -44,10 +44,8 @@ class OfflinePlayer implements IPlayer, Metadatable{
public function __construct(Server $server, string $name){
$this->server = $server;
$this->name = $name;
if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->name) . ".dat")){
if($this->server->hasOfflinePlayerData($this->name)){
$this->namedtag = $this->server->getOfflinePlayerData($this->name);
}else{
$this->namedtag = null;
}
}

View File

@ -101,6 +101,7 @@ use pocketmine\network\mcpe\PlayerNetworkSessionAdapter;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
@ -120,6 +121,7 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
@ -276,7 +278,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var int */
protected $spawnThreshold;
/** @var int */
protected $chunkLoadCount = 0;
protected $spawnChunkLoadCount = 0;
/** @var int */
protected $chunksPerTick;
@ -285,14 +287,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var Vector3|null */
protected $newPosition;
/** @var Vector3|null */
public $speed = null;
/** @var bool */
protected $isTeleporting = false;
/** @var int */
protected $inAirTicks = 0;
/** @var int */
protected $startAirTicks = 5;
/** @var float */
protected $stepHeight = 0.6;
/** @var bool */
@ -547,9 +545,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function resetFallDistance() : void{
parent::resetFallDistance();
if($this->inAirTicks !== 0){
$this->startAirTicks = 5;
}
$this->inAirTicks = 0;
}
@ -801,7 +796,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$ev = new PlayerChangeSkinEvent($this, $this->getSkin(), $skin);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->sendSkin([$this]);
@ -969,8 +964,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->usedChunks[Level::chunkHash($x, $z)] = true;
$this->chunkLoadCount++;
$this->dataPacket($payload);
if($this->spawned){
@ -981,8 +974,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($this->chunkLoadCount >= $this->spawnThreshold and !$this->spawned){
$this->doFirstSpawn();
if($this->spawnChunkLoadCount !== -1 and ++$this->spawnChunkLoadCount >= $this->spawnThreshold){
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
$this->spawnChunkLoadCount = -1;
}
}
@ -1020,11 +1014,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
Timings::$playerChunkSendTimer->stopTiming();
}
protected function doFirstSpawn(){
public function doFirstSpawn(){
if($this->spawned){
return; //avoid player spawning twice (this can only happen on 3.x with a custom malicious client)
}
$this->spawned = true;
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
@ -1032,11 +1027,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
$this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this,
$ev = new PlayerJoinEvent($this,
new TranslationContainer(TextFormat::YELLOW . "%multiplayer.player.joined", [
$this->getDisplayName()
])
));
);
$ev->call();
if(strlen(trim((string) $ev->getJoinMessage())) > 0){
$this->server->broadcastMessage($ev->getJoinMessage());
}
@ -1155,6 +1151,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->loadQueue = $newOrder;
if(!empty($this->loadQueue) or !empty($unloadChunks)){
$pk = new NetworkChunkPublisherUpdatePacket();
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
$pk->z = $this->getFloorZ();
$pk->radius = $this->viewDistance * 16; //blocks, not chunks >.>
$this->dataPacket($pk);
}
Timings::$playerChunkOrderTimer->stopTiming();
}
@ -1221,7 +1225,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pos = $pos->floor();
$b = $this->level->getBlock($pos);
$this->server->getPluginManager()->callEvent($ev = new PlayerBedEnterEvent($this, $b));
$ev = new PlayerBedEnterEvent($this, $b);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1248,7 +1253,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if($b instanceof Bed){
$b->setOccupied(false);
}
$this->server->getPluginManager()->callEvent($ev = new PlayerBedLeaveEvent($this, $b));
(new PlayerBedLeaveEvent($this, $b))->call();
$this->sleeping = null;
$this->propertyManager->setBlockPos(self::DATA_PLAYER_BED_POSITION, null);
@ -1288,7 +1293,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
}
$this->server->getPluginManager()->callEvent($ev = new PlayerAchievementAwardedEvent($this, $achievementId));
$ev = new PlayerAchievementAwardedEvent($this, $achievementId);
$ev->call();
if(!$ev->isCancelled()){
$this->achievements[$achievementId] = true;
Achievement::broadcast($this, $achievementId);
@ -1352,7 +1358,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
$this->server->getPluginManager()->callEvent($ev = new PlayerGameModeChangeEvent($this, $gm));
$ev = new PlayerGameModeChangeEvent($this, $gm);
$ev->call();
if($ev->isCancelled()){
if($client){ //gamemode change by client in the GUI
$this->sendGamemode();
@ -1546,14 +1553,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->server->getLogger()->warning($this->getName() . " moved too fast, reverting movement");
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $this->newPosition);
$revert = true;
}else{
$chunkX = $newPos->getFloorX() >> 4;
$chunkZ = $newPos->getFloorZ() >> 4;
if(!$this->level->isChunkLoaded($chunkX, $chunkZ) or !$this->level->isChunkGenerated($chunkX, $chunkZ)){
$revert = true;
$this->nextChunkOrderRun = 0;
}
}elseif(!$this->level->isInLoadedTerrain($newPos) or !$this->level->isChunkGenerated($newPos->getFloorX() >> 4, $newPos->getFloorZ() >> 4)){
$revert = true;
$this->nextChunkOrderRun = 0;
}
if(!$revert and $distanceSquared != 0){
@ -1569,7 +1571,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev = new PlayerIllegalMoveEvent($this, $newPos, new Vector3($this->lastX, $this->lastY, $this->lastZ));
$ev->setCancelled($this->allowMovementCheats);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$revert = true;
@ -1599,7 +1601,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev = new PlayerMoveEvent($this, $from, $to);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!($revert = $ev->isCancelled())){ //Yes, this is intended
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
@ -1616,10 +1618,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
}
$this->speed = $to->subtract($from)->divide($tickDiff);
}elseif($distanceSquared == 0){
$this->speed = new Vector3(0, 0, 0);
}
if($revert){
@ -1643,7 +1641,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function jump() : void{
$this->server->getPluginManager()->callEvent(new PlayerJumpEvent($this));
(new PlayerJumpEvent($this))->call();
parent::jump();
}
@ -1651,10 +1649,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(parent::setMotion($motion)){
$this->broadcastMotion();
if($this->motion->y > 0){
$this->startAirTicks = (-log($this->gravity / ($this->gravity + $this->drag * $this->motion->y)) / $this->drag) * 2 + 5;
}
return true;
}
return false;
@ -1708,6 +1702,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if($this->spawned){
$this->processMovement($tickDiff);
$this->motion->x = $this->motion->y = $this->motion->z = 0; //TODO: HACK! (Fixes player knockback being messed up)
if($this->onGround){
$this->inAirTicks = 0;
}else{
$this->inAirTicks += $tickDiff;
}
Timings::$timerEntityBaseTick->startTiming();
$this->entityBaseTick($tickDiff);
@ -1717,32 +1716,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
Timings::$playerCheckNearEntitiesTimer->startTiming();
$this->checkNearEntities();
Timings::$playerCheckNearEntitiesTimer->stopTiming();
if($this->speed !== null){
if($this->onGround){
if($this->inAirTicks !== 0){
$this->startAirTicks = 5;
}
$this->inAirTicks = 0;
}else{
if(!$this->allowFlight and $this->inAirTicks > 10 and !$this->isSleeping() and !$this->isImmobile()){
$expectedVelocity = (-$this->gravity) / $this->drag - ((-$this->gravity) / $this->drag) * exp(-$this->drag * ($this->inAirTicks - $this->startAirTicks));
$diff = ($this->speed->y - $expectedVelocity) ** 2;
if(!$this->hasEffect(Effect::JUMP) and !$this->hasEffect(Effect::LEVITATION) and $diff > 0.6 and $expectedVelocity < $this->speed->y and !$this->server->getAllowFlight()){
if($this->inAirTicks < 100){
$this->setMotion(new Vector3(0, $expectedVelocity, 0));
}elseif($this->kick($this->server->getLanguage()->translateString("kick.reason.cheat", ["%ability.flight"]))){
$this->timings->stopTiming();
return false;
}
}
}
$this->inAirTicks += $tickDiff;
}
}
}
}
@ -1900,7 +1873,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->setSkin($skin);
$this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason"));
$ev = new PlayerPreLoginEvent($this, "Plugin reason");
$ev->call();
if($ev->isCancelled()){
$this->close("", $ev->getKickMessage());
@ -2079,7 +2053,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->level->registerChunkLoader($this, ((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4, true);
parent::__construct($this->level, $this->namedtag);
$this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason"));
$ev = new PlayerLoginEvent($this, "Plugin reason");
$ev->call();
if($ev->isCancelled()){
$this->close($this->getLeaveMessage(), $ev->getKickMessage());
@ -2122,6 +2097,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk->worldName = $this->server->getMotd();
$this->dataPacket($pk);
$this->sendDataPacket(new AvailableEntityIdentifiersPacket());
$this->level->sendTime($this);
$this->sendAttributes(true);
@ -2181,8 +2158,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$ev = new PlayerCommandPreprocessEvent($this, $messagePart);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
break;
@ -2193,7 +2169,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1));
Timings::$playerCommandTimer->stopTiming();
}else{
$this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage()));
$ev = new PlayerChatEvent($this, $ev->getMessage());
$ev->call();
if(!$ev->isCancelled()){
$this->server->broadcastMessage($this->getServer()->getLanguage()->translateString($ev->getFormat(), [$ev->getPlayer()->getDisplayName(), $ev->getMessage()]), $ev->getRecipients());
}
@ -2236,9 +2213,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
//TODO: add events so plugins can change this
if($this->chunk !== null){
$this->getLevel()->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $packet);
}
$this->getLevel()->broadcastPacketToViewers($this, $packet);
return true;
}
@ -2450,8 +2425,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->inventory->sendHeldItem($this);
return true;
@ -2596,7 +2570,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if($this->hasItemCooldown($slot)){
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled() or !$this->consumeObject($slot)){
$this->inventory->sendContents($this);
@ -2657,6 +2631,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(!$this->spawned or !$this->isAlive()){
return true;
}
if($packet->action === InteractPacket::ACTION_MOUSEOVER and $packet->target === 0){
//TODO HACK: silence useless spam (MCPE 1.8)
//this packet is EXPECTED to only be sent when interacting with an entity, but due to some messy Mojang
//hacks, it also sends it when changing the held item now, which causes us to think the inventory was closed
//when it wasn't.
return true;
}
$this->doCloseInventory();
@ -2699,7 +2680,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$this->inventory->setItemInHand($ev->getResultItem());
}
@ -2729,7 +2710,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev->setCancelled();
}
$this->getServer()->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->inventory->sendHeldItem($this);
break;
@ -2808,7 +2789,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function toggleSprint(bool $sprint) : void{
$ev = new PlayerToggleSprintEvent($this, $sprint);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->sendData($this);
}else{
@ -2818,7 +2799,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function toggleSneak(bool $sneak) : void{
$ev = new PlayerToggleSneakEvent($this, $sneak);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->sendData($this);
}else{
@ -2831,7 +2812,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
$this->server->getPluginManager()->callEvent($ev = new PlayerAnimationEvent($this, $packet->action));
$ev = new PlayerAnimationEvent($this, $packet->action);
$ev->call();
if($ev->isCancelled()){
return true;
}
@ -2876,7 +2858,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->doCloseInventory();
if(isset($this->windowIndex[$packet->windowId])){
$this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this));
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
$this->removeWindow($this->windowIndex[$packet->windowId]);
return true;
}elseif($packet->windowId === 255){
@ -2895,11 +2877,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$handled = false;
$isFlying = $packet->getFlag(AdventureSettingsPacket::FLYING);
if($isFlying and !$this->allowFlight and !$this->server->getAllowFlight()){
if($isFlying and !$this->allowFlight){
$this->kick($this->server->getLanguage()->translateString("kick.reason.cheat", ["%ability.flight"]));
return true;
}elseif($isFlying !== $this->isFlying()){
$this->server->getPluginManager()->callEvent($ev = new PlayerToggleFlightEvent($this, $isFlying));
$ev = new PlayerToggleFlightEvent($this, $isFlying);
$ev->call();
if($ev->isCancelled()){
$this->sendSettings();
}else{
@ -2967,7 +2950,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$tile->spawnTo($this);
return true;
@ -3040,7 +3023,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
$this->getServer()->getPluginManager()->callEvent($event = new PlayerEditBookEvent($this, $oldBook, $newBook, $packet->type, $modifiedPages));
$event = new PlayerEditBookEvent($this, $oldBook, $newBook, $packet->type, $modifiedPages);
$event->call();
if($event->isCancelled()){
return true;
}
@ -3075,7 +3059,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming();
$this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
$ev = new DataPacketSendEvent($this, $packet);
$ev->call();
if($ev->isCancelled()){
$timings->stopTiming();
return false;
@ -3106,7 +3091,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming();
try{
$this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
$ev = new DataPacketSendEvent($this, $packet);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -3154,8 +3140,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool if transfer was successful.
*/
public function transfer(string $address, int $port = 19132, string $message = "transfer") : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerTransferEvent($this, $address, $port, $message));
$ev = new PlayerTransferEvent($this, $address, $port, $message);
$ev->call();
if(!$ev->isCancelled()){
$pk = new TransferPacket();
$pk->address = $ev->getAddress();
@ -3179,7 +3165,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool
*/
public function kick(string $reason = "", bool $isAdmin = true, $quitMessage = null) : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage()));
$ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage());
$ev->call();
if(!$ev->isCancelled()){
$reason = $ev->getReason();
$message = $reason;
@ -3427,7 +3414,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->stopSleep();
if($this->spawned){
$this->server->getPluginManager()->callEvent($ev = new PlayerQuitEvent($this, $message, $reason));
$ev = new PlayerQuitEvent($this, $message, $reason);
$ev->call();
if($ev->getQuitMessage() != ""){
$this->server->broadcastMessage($ev->getQuitMessage());
}
@ -3573,7 +3561,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
//main inventory and drops the rest on the ground.
$this->doCloseInventory();
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops()));
$ev = new PlayerDeathEvent($this, $this->getDrops());
$ev->call();
if(!$ev->getKeepInventory()){
foreach($ev->getDrops() as $item){
@ -3608,7 +3597,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return;
}
$this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn()));
$ev = new PlayerRespawnEvent($this, $this->getSpawn());
$ev->call();
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getLevel());
$this->teleport($realSpawn);
@ -3806,6 +3796,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @param bool $isPermanent Prevents the window being removed if true.
*
* @return int
*
* @throws \InvalidArgumentException if a forceID which is already in use is specified
* @throws \InvalidStateException if trying to add a window without forceID when no slots are free
*/
public function addWindow(Inventory $inventory, int $forceId = null, bool $isPermanent = false) : int{
if(($id = $this->getWindowId($inventory)) !== ContainerIds::NONE){
@ -3813,10 +3806,21 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
if($forceId === null){
$this->windowCnt = $cnt = max(ContainerIds::FIRST, ++$this->windowCnt % ContainerIds::LAST);
$cnt = $this->windowCnt;
do{
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % ContainerIds::LAST);
if($cnt === $this->windowCnt){ //wraparound, no free slots
throw new \InvalidStateException("No free window IDs found");
}
}while(isset($this->windowIndex[$cnt]));
$this->windowCnt = $cnt;
}else{
$cnt = $forceId;
if(isset($this->windowIndex[$cnt])){
throw new \InvalidArgumentException("Requested force ID $forceId already in use");
}
}
$this->windowIndex[$cnt] = $inventory;
$this->windows[spl_object_hash($inventory)] = $cnt;
if($inventory->open($this)){

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.3.3";
const BASE_VERSION = "3.5.1";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -197,9 +197,6 @@ class Server{
/** @var ResourcePackManager */
private $resourceManager;
/** @var ConsoleCommandSender */
private $consoleSender;
/** @var int */
private $maxPlayers;
@ -555,10 +552,11 @@ class Server{
}
/**
* @deprecated
* @return bool
*/
public function getAllowFlight() : bool{
return $this->getConfigBool("allow-flight", false);
return true;
}
/**
@ -733,6 +731,17 @@ class Server{
return $result;
}
/**
* Returns whether the server has stored any saved data for this player.
*
* @param string $name
*
* @return bool
*/
public function hasOfflinePlayerData(string $name) : bool{
return file_exists($this->getDataPath() . "players/$name.dat");
}
/**
* @param string $name
*
@ -809,7 +818,7 @@ class Server{
$ev = new PlayerDataSaveEvent($nbtTag, $name);
$ev->setCancelled(!$this->shouldSavePlayerData());
$this->pluginManager->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$nbt = new BigEndianNBTStream();
@ -1028,7 +1037,7 @@ class Server{
$this->levels[$level->getId()] = $level;
$this->getPluginManager()->callEvent(new LevelLoadEvent($level));
(new LevelLoadEvent($level))->call();
$level->setTickRate($this->baseTickRate);
@ -1077,9 +1086,9 @@ class Server{
$level->setTickRate($this->baseTickRate);
$this->getPluginManager()->callEvent(new LevelInitEvent($level));
(new LevelInitEvent($level))->call();
$this->getPluginManager()->callEvent(new LevelLoadEvent($level));
(new LevelLoadEvent($level))->call();
$this->getLogger()->notice($this->getLanguage()->translateString("pocketmine.level.backgroundGeneration", [$name]));
@ -1432,10 +1441,38 @@ class Server{
}
$this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []);
$this->logger->info("Loading server properties...");
$this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [
"motd" => \pocketmine\NAME . " Server",
"server-port" => 19132,
"white-list" => false,
"announce-player-achievements" => true,
"spawn-protection" => 16,
"max-players" => 20,
"spawn-animals" => true,
"spawn-mobs" => true,
"gamemode" => 0,
"force-gamemode" => false,
"hardcore" => false,
"pvp" => true,
"difficulty" => 1,
"generator-settings" => "",
"level-name" => "world",
"level-seed" => "",
"level-type" => "DEFAULT",
"enable-query" => true,
"enable-rcon" => false,
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
"auto-save" => true,
"view-distance" => 8,
"xbox-auth" => true,
"language" => "eng"
]);
define('pocketmine\DEBUG', (int) $this->getProperty("debug.level", 1));
$this->forceLanguage = (bool) $this->getProperty("settings.force-language", false);
$this->baseLang = new BaseLang($this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE));
$this->baseLang = new BaseLang($this->getConfigString("language", $this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE)));
$this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()]));
if(\pocketmine\IS_DEVELOPMENT_BUILD and !((bool) $this->getProperty("settings.enable-dev-builds", false))){
@ -1458,34 +1495,6 @@ class Server{
$this->logger->setLogDebug(\pocketmine\DEBUG > 1);
}
$this->logger->info("Loading server properties...");
$this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [
"motd" => \pocketmine\NAME . " Server",
"server-port" => 19132,
"white-list" => false,
"announce-player-achievements" => true,
"spawn-protection" => 16,
"max-players" => 20,
"allow-flight" => false,
"spawn-animals" => true,
"spawn-mobs" => true,
"gamemode" => 0,
"force-gamemode" => false,
"hardcore" => false,
"pvp" => true,
"difficulty" => 1,
"generator-settings" => "",
"level-name" => "world",
"level-seed" => "",
"level-type" => "DEFAULT",
"enable-query" => true,
"enable-rcon" => false,
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
"auto-save" => true,
"view-distance" => 8,
"xbox-auth" => true
]);
$this->memoryManager = new MemoryManager($this);
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.start", [TextFormat::AQUA . $this->getVersion() . TextFormat::RESET]));
@ -1523,10 +1532,22 @@ class Server{
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
$consoleSender = new ConsoleCommandSender();
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender);
$consoleNotifier = new SleeperNotifier();
$this->console = new CommandReader($consoleNotifier);
$this->tickSleeper->addNotifier($consoleNotifier, function() : void{
$this->checkConsole();
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($consoleSender) : void{
Timings::$serverCommandTimer->startTiming();
while(($line = $this->console->getLine()) !== null){
$ev = new ServerCommandEvent($consoleSender, $line);
$ev->call();
if(!$ev->isCancelled()){
$this->dispatchCommand($ev->getSender(), $ev->getCommand());
}
}
Timings::$serverCommandTimer->stopTiming();
});
$this->console->start(PTHREADS_INHERIT_NONE);
@ -1602,7 +1623,6 @@ class Server{
Timings::init();
TimingsHandler::setEnabled((bool) $this->getProperty("settings.enable-profiling", false));
$this->consoleSender = new ConsoleCommandSender();
$this->commandMap = new SimpleCommandMap($this);
Entity::init();
@ -1619,7 +1639,6 @@ class Server{
$this->resourceManager = new ResourcePackManager($this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR, $this->logger);
$this->pluginManager = new PluginManager($this, $this->commandMap, ((bool) $this->getProperty("plugins.legacy-data-dir", true)) ? null : $this->getDataPath() . "plugin_data" . DIRECTORY_SEPARATOR);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
$this->profilingTickRate = (float) $this->getProperty("settings.profile-report-trigger", 20);
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
$this->pluginManager->registerInterface(new ScriptPluginLoader());
@ -1918,17 +1937,6 @@ class Server{
$this->pluginManager->disablePlugins();
}
public function checkConsole(){
Timings::$serverCommandTimer->startTiming();
while(($line = $this->console->getLine()) !== null){
$this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line));
if(!$ev->isCancelled()){
$this->dispatchCommand($ev->getSender(), $ev->getCommand());
}
}
Timings::$serverCommandTimer->stopTiming();
}
/**
* Executes a command from a CommandSender
*
@ -1940,7 +1948,8 @@ class Server{
*/
public function dispatchCommand(CommandSender $sender, string $commandLine, bool $internal = false) : bool{
if(!$internal){
$this->pluginManager->callEvent($ev = new CommandEvent($sender, $commandLine));
$ev = new CommandEvent($sender, $commandLine);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -2535,7 +2544,7 @@ class Server{
}
if(($this->tickCounter & 0b111111111) === 0){
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5))->call();
if($this->queryHandler !== null){
$this->queryHandler->regenerateInfo();
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
class ActivatorRail extends Rail{
class ActivatorRail extends RedstoneRail{
protected $id = self::ACTIVATOR_RAIL;

View File

@ -0,0 +1,266 @@
<?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\item\Item;
use pocketmine\math\Vector3;
use pocketmine\Player;
abstract class BaseRail extends Flowable{
public const STRAIGHT_NORTH_SOUTH = 0;
public const STRAIGHT_EAST_WEST = 1;
public const ASCENDING_EAST = 2;
public const ASCENDING_WEST = 3;
public const ASCENDING_NORTH = 4;
public const ASCENDING_SOUTH = 5;
private const ASCENDING_SIDES = [
self::ASCENDING_NORTH => Vector3::SIDE_NORTH,
self::ASCENDING_EAST => Vector3::SIDE_EAST,
self::ASCENDING_SOUTH => Vector3::SIDE_SOUTH,
self::ASCENDING_WEST => Vector3::SIDE_WEST
];
protected const FLAG_ASCEND = 1 << 24; //used to indicate direction-up
protected const CONNECTIONS = [
//straights
self::STRAIGHT_NORTH_SOUTH => [
Vector3::SIDE_NORTH,
Vector3::SIDE_SOUTH
],
self::STRAIGHT_EAST_WEST => [
Vector3::SIDE_EAST,
Vector3::SIDE_WEST
],
//ascending
self::ASCENDING_EAST => [
Vector3::SIDE_WEST,
Vector3::SIDE_EAST | self::FLAG_ASCEND
],
self::ASCENDING_WEST => [
Vector3::SIDE_EAST,
Vector3::SIDE_WEST | self::FLAG_ASCEND
],
self::ASCENDING_NORTH => [
Vector3::SIDE_SOUTH,
Vector3::SIDE_NORTH | self::FLAG_ASCEND
],
self::ASCENDING_SOUTH => [
Vector3::SIDE_NORTH,
Vector3::SIDE_SOUTH | self::FLAG_ASCEND
]
];
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getHardness() : float{
return 0.7;
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent() and $this->getLevel()->setBlock($blockReplace, $this, true, true)){
$this->tryReconnect();
return true;
}
return false;
}
protected static function searchState(array $connections, array $lookup) : int{
$meta = array_search($connections, $lookup, true);
if($meta === false){
$meta = array_search(array_reverse($connections), $lookup, true);
}
if($meta === false){
throw new \InvalidArgumentException("No meta value matches connections " . implode(", ", array_map('dechex', $connections)));
}
return $meta;
}
/**
* Returns a meta value for the rail with the given connections.
*
* @param array $connections
*
* @return int
*
* @throws \InvalidArgumentException if no state matches the given connections
*/
protected function getMetaForState(array $connections) : int{
return self::searchState($connections, self::CONNECTIONS);
}
/**
* Returns the connection directions of this rail (depending on the current block state)
*
* @return int[]
*/
abstract protected function getConnectionsForState() : array;
/**
* Returns all the directions this rail is already connected in.
*
* @return int[]
*/
private function getConnectedDirections() : array{
/** @var int[] $connections */
$connections = [];
/** @var int $connection */
foreach($this->getConnectionsForState() as $connection){
$other = $this->getSide($connection & ~self::FLAG_ASCEND);
$otherConnection = Vector3::getOppositeSide($connection & ~self::FLAG_ASCEND);
if(($connection & self::FLAG_ASCEND) !== 0){
$other = $other->getSide(Vector3::SIDE_UP);
}elseif(!($other instanceof BaseRail)){ //check for rail sloping up to meet this one
$other = $other->getSide(Vector3::SIDE_DOWN);
$otherConnection |= self::FLAG_ASCEND;
}
if(
$other instanceof BaseRail and
in_array($otherConnection, $other->getConnectionsForState(), true)
){
$connections[] = $connection;
}
}
return $connections;
}
private function getPossibleConnectionDirections(array $constraints) : array{
switch(count($constraints)){
case 0:
//No constraints, can connect in any direction
$possible = [
Vector3::SIDE_NORTH => true,
Vector3::SIDE_SOUTH => true,
Vector3::SIDE_WEST => true,
Vector3::SIDE_EAST => true
];
foreach($possible as $p => $_){
$possible[$p | self::FLAG_ASCEND] = true;
}
return $possible;
case 1:
return $this->getPossibleConnectionDirectionsOneConstraint(array_shift($constraints));
case 2:
return [];
default:
throw new \InvalidArgumentException("Expected at most 2 constraints, got " . count($constraints));
}
}
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
$opposite = Vector3::getOppositeSide($constraint & ~self::FLAG_ASCEND);
$possible = [$opposite => true];
if(($constraint & self::FLAG_ASCEND) === 0){
//We can slope the other way if this connection isn't already a slope
$possible[$opposite | self::FLAG_ASCEND] = true;
}
return $possible;
}
private function tryReconnect() : void{
$thisConnections = $this->getConnectedDirections();
$changed = false;
do{
$possible = $this->getPossibleConnectionDirections($thisConnections);
$continue = false;
foreach($possible as $thisSide => $_){
$otherSide = Vector3::getOppositeSide($thisSide & ~self::FLAG_ASCEND);
$other = $this->getSide($thisSide & ~self::FLAG_ASCEND);
if(($thisSide & self::FLAG_ASCEND) !== 0){
$other = $other->getSide(Vector3::SIDE_UP);
}elseif(!($other instanceof BaseRail)){ //check if other rails can slope up to meet this one
$other = $other->getSide(Vector3::SIDE_DOWN);
$otherSide |= self::FLAG_ASCEND;
}
if(!($other instanceof BaseRail) or count($otherConnections = $other->getConnectedDirections()) >= 2){
//we can only connect to a rail that has less than 2 connections
continue;
}
$otherPossible = $other->getPossibleConnectionDirections($otherConnections);
if(isset($otherPossible[$otherSide])){
$otherConnections[] = $otherSide;
$other->updateState($otherConnections);
$changed = true;
$thisConnections[] = $thisSide;
$continue = count($thisConnections) < 2;
break; //force recomputing possible directions, since this connection could invalidate others
}
}
}while($continue);
if($changed){
$this->updateState($thisConnections);
}
}
private function updateState(array $connections) : void{
if(count($connections) === 1){
$connections[] = Vector3::getOppositeSide($connections[0] & ~self::FLAG_ASCEND);
}elseif(count($connections) !== 2){
throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections));
}
$this->meta = $this->getMetaForState($connections);
$this->level->setBlock($this, $this, false, false); //avoid recursion
}
public function onNearbyBlockChange() : void{
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent() or (
isset(self::ASCENDING_SIDES[$this->meta & 0x07]) and
$this->getSide(self::ASCENDING_SIDES[$this->meta & 0x07])->isTransparent()
)){
$this->getLevel()->useBreakOn($this);
}
}
public function getVariantBitmask() : int{
return 0;
}
}

View File

@ -31,7 +31,6 @@ use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\Server;
class Cactus extends Transparent{
@ -95,7 +94,8 @@ class Cactus extends Transparent{
for($y = 1; $y < 3; ++$y){
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
if($b->getId() === self::AIR){
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS)));
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
}

View File

@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\Server;
abstract class Crops extends Flowable{
@ -50,8 +49,8 @@ abstract class Crops extends Flowable{
$block->meta = 7;
}
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
}
@ -79,8 +78,8 @@ abstract class Crops extends Flowable{
if($this->meta < 0x07){
$block = clone $this;
++$block->meta;
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
class DetectorRail extends Rail{
class DetectorRail extends RedstoneRail{
protected $id = self::DETECTOR_RAIL;

View File

@ -31,7 +31,6 @@ use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\Server;
class Fire extends Flowable{
@ -69,7 +68,7 @@ class Fire extends Flowable{
if($entity instanceof Arrow){
$ev->setCancelled();
}
Server::getInstance()->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$entity->setOnFire($ev->getDuration());
}
@ -153,7 +152,8 @@ class Fire extends Flowable{
private function burnBlock(Block $block, int $chanceBound) : void{
if(mt_rand(0, $chanceBound) < $block->getFlammability()){
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockBurnEvent($block, $this));
$ev = new BlockBurnEvent($block, $this);
$ev->call();
if(!$ev->isCancelled()){
$block->onIncinerate();

View File

@ -67,7 +67,8 @@ class Grass extends Solid{
$lightAbove = $this->level->getFullLightAt($this->x, $this->y + 1, $this->z);
if($lightAbove < 4 and BlockFactory::$lightFilter[$this->level->getBlockIdAt($this->x, $this->y + 1, $this->z)] >= 3){ //2 plus 1 standard filter amount
//grass dies
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT)));
$ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT));
$ev->call();
if(!$ev->isCancelled()){
$this->level->setBlock($this, $ev->getNewState(), false, false);
}
@ -86,7 +87,8 @@ class Grass extends Solid{
continue;
}
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($b = $this->level->getBlockAt($x, $y, $z), $this, BlockFactory::get(Block::GRASS)));
$ev = new BlockSpreadEvent($b = $this->level->getBlockAt($x, $y, $z), $this, BlockFactory::get(Block::GRASS));
$ev->call();
if(!$ev->isCancelled()){
$this->level->setBlock($b, $ev->getNewState(), false, false);
}

View File

@ -31,7 +31,6 @@ use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\Player;
use pocketmine\Server;
class Lava extends Liquid{
@ -107,7 +106,7 @@ class Lava extends Liquid{
$entity->attack($ev);
$ev = new EntityCombustByBlockEvent($this, $entity, 15);
Server::getInstance()->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$entity->setOnFire($ev->getDuration());
}

View File

@ -147,8 +147,8 @@ class Leaves extends Transparent{
$this->meta &= 0x03;
$visited = [];
$this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new LeavesDecayEvent($this));
$ev = new LeavesDecayEvent($this);
$ev->call();
if($ev->isCancelled() or $this->findLog($this, $visited, 0)){
$this->getLevel()->setBlock($this, $this, false, false);
}else{

View File

@ -24,11 +24,13 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockFormEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\level\Level;
use pocketmine\level\sound\FizzSound;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
abstract class Liquid extends Transparent{
@ -294,12 +296,16 @@ abstract class Liquid extends Transparent{
protected function flowIntoBlock(Block $block, int $newFlowDecay) : void{
if($this->canFlowInto($block) and !($block instanceof Liquid)){
if($block->getId() > 0){
$this->level->useBreakOn($block);
}
$ev = new BlockSpreadEvent($block, $this, BlockFactory::get($this->getId(), $newFlowDecay));
$ev->call();
if(!$ev->isCancelled()){
if($block->getId() > 0){
$this->level->useBreakOn($block);
}
$this->level->setBlock($block, BlockFactory::get($this->getId(), $newFlowDecay), true, true);
$this->level->scheduleDelayedBlockUpdate($block, $this->tickRate());
$this->level->setBlock($block, $ev->getNewState(), true, true);
$this->level->scheduleDelayedBlockUpdate($block, $this->tickRate());
}
}
}
@ -425,10 +431,12 @@ abstract class Liquid extends Transparent{
}
protected function liquidCollide(Block $cause, Block $result) : bool{
//TODO: add events
$this->level->setBlock($this, $result, true, true);
$this->level->broadcastLevelSoundEvent($this->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_FIZZ, (int) ((2.6 + (lcg_value() - lcg_value()) * 0.8) * 1000));
$ev = new BlockFormEvent($this, $result);
$ev->call();
if(!$ev->isCancelled()){
$this->level->setBlock($this, $ev->getNewState(), true, true);
$this->level->addSound(new FizzSound($this->add(0.5, 0.5, 0.5), 2.6 + (lcg_value() - lcg_value()) * 0.8));
}
return true;
}

View File

@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\Server;
class MelonStem extends Crops{
@ -46,7 +45,8 @@ class MelonStem extends Crops{
if($this->meta < 0x07){
$block = clone $this;
++$block->meta;
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
}
@ -60,7 +60,8 @@ class MelonStem extends Crops{
$side = $this->getSide(mt_rand(2, 5));
$d = $side->getSide(Vector3::SIDE_DOWN);
if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, BlockFactory::get(Block::MELON_BLOCK)));
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::MELON_BLOCK));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
}

View File

@ -27,7 +27,6 @@ use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\Server;
class Mycelium extends Solid{
@ -67,7 +66,8 @@ class Mycelium extends Solid{
$block = $this->getLevel()->getBlockAt($x, $y, $z);
if($block->getId() === Block::DIRT){
if($block->getSide(Vector3::SIDE_UP) instanceof Transparent){
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($block, $this, BlockFactory::get(Block::MYCELIUM)));
$ev = new BlockSpreadEvent($block, $this, BlockFactory::get(Block::MYCELIUM));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($block, $ev->getNewState());
}

View File

@ -68,8 +68,8 @@ class NetherWartPlant extends Flowable{
if($this->meta < 3 and mt_rand(0, 10) === 0){ //Still growing
$block = clone $this;
$block->meta++;
$this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($this, $ev->getNewState(), false, true);
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
class PoweredRail extends Rail{
class PoweredRail extends RedstoneRail{
protected $id = self::POWERED_RAIL;
public function getName() : string{

View File

@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\Server;
class PumpkinStem extends Crops{
@ -46,7 +45,8 @@ class PumpkinStem extends Crops{
if($this->meta < 0x07){
$block = clone $this;
++$block->meta;
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block));
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
}
@ -60,7 +60,8 @@ class PumpkinStem extends Crops{
$side = $this->getSide(mt_rand(2, 5));
$d = $side->getSide(Vector3::SIDE_DOWN);
if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, BlockFactory::get(Block::PUMPKIN)));
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::PUMPKIN));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
}

View File

@ -23,54 +23,71 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\Player;
class Rail extends Flowable{
class Rail extends BaseRail{
public const STRAIGHT_NORTH_SOUTH = 0;
public const STRAIGHT_EAST_WEST = 1;
public const ASCENDING_EAST = 2;
public const ASCENDING_WEST = 3;
public const ASCENDING_NORTH = 4;
public const ASCENDING_SOUTH = 5;
/* extended meta values for regular rails, to allow curving */
public const CURVE_SOUTHEAST = 6;
public const CURVE_SOUTHWEST = 7;
public const CURVE_NORTHWEST = 8;
public const CURVE_NORTHEAST = 9;
protected $id = self::RAIL;
private const CURVE_CONNECTIONS = [
self::CURVE_SOUTHEAST => [
Vector3::SIDE_SOUTH,
Vector3::SIDE_EAST
],
self::CURVE_SOUTHWEST => [
Vector3::SIDE_SOUTH,
Vector3::SIDE_WEST
],
self::CURVE_NORTHWEST => [
Vector3::SIDE_NORTH,
Vector3::SIDE_WEST
],
self::CURVE_NORTHEAST => [
Vector3::SIDE_NORTH,
Vector3::SIDE_EAST
]
];
public function __construct(int $meta = 0){
$this->meta = $meta;
}
protected $id = self::RAIL;
public function getName() : string{
return "Rail";
}
public function getHardness() : float{
return 0.7;
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent()){
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
$this->getLevel()->useBreakOn($this);
}else{
//TODO: Update rail connectivity
protected function getMetaForState(array $connections) : int{
try{
return self::searchState($connections, self::CURVE_CONNECTIONS);
}catch(\InvalidArgumentException $e){
return parent::getMetaForState($connections);
}
}
public function getVariantBitmask() : int{
return 0;
protected function getConnectionsForState() : array{
return self::CURVE_CONNECTIONS[$this->meta] ?? self::CONNECTIONS[$this->meta];
}
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
static $horizontal = [
Vector3::SIDE_NORTH,
Vector3::SIDE_SOUTH,
Vector3::SIDE_WEST,
Vector3::SIDE_EAST
];
$possible = parent::getPossibleConnectionDirectionsOneConstraint($constraint);
if(($constraint & self::FLAG_ASCEND) === 0){
foreach($horizontal as $d){
if($constraint !== $d){
$possible[$d] = true;
}
}
}
return $possible;
}
}

View File

@ -0,0 +1,32 @@
<?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;
class RedstoneRail extends BaseRail{
protected const FLAG_POWERED = 0x08;
protected function getConnectionsForState() : array{
return self::CONNECTIONS[$this->meta & ~self::FLAG_POWERED];
}
}

View File

@ -27,7 +27,6 @@ use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\Server;
class Sugarcane extends Flowable{
@ -49,7 +48,8 @@ class Sugarcane extends Flowable{
for($y = 1; $y < 3; ++$y){
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
if($b->getId() === self::AIR){
Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK)));
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
}

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\lang\TranslationContainer;
use pocketmine\Server;
use pocketmine\utils\TextFormat;
@ -49,11 +48,6 @@ class FormattedCommandAlias extends Command{
$commands[] = $this->buildCommand($formatString, $args);
}catch(\InvalidArgumentException $e){
$sender->sendMessage(TextFormat::RED . $e->getMessage());
return false;
}catch(\Throwable $e){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception"));
$sender->getServer()->getLogger()->logException($e);
return false;
}
}

View File

@ -65,9 +65,7 @@ use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\TranslationContainer;
use pocketmine\Server;
use pocketmine\utils\TextFormat;
class SimpleCommandMap implements CommandMap{
@ -258,14 +256,10 @@ class SimpleCommandMap implements CommandMap{
$target->execute($sender, $sentCommandLabel, $args);
}catch(InvalidCommandSyntaxException $e){
$sender->sendMessage($this->server->getLanguage()->translateString("commands.generic.usage", [$target->getUsage()]));
}catch(\Throwable $e){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception"));
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.command.exception", [$commandLine, (string) $target, $e->getMessage()]));
$sender->getServer()->getLogger()->logException($e);
}finally{
$target->timings->stopTiming();
}
$target->timings->stopTiming();
return true;
}

View File

@ -34,7 +34,8 @@ class PardonCommand extends VanillaCommand{
parent::__construct(
$name,
"%pocketmine.command.unban.player.description",
"%commands.unban.usage"
"%commands.unban.usage",
["unban"]
);
$this->setPermission("pocketmine.command.unban.player");
}

View File

@ -34,7 +34,8 @@ class PardonIpCommand extends VanillaCommand{
parent::__construct(
$name,
"%pocketmine.command.unban.ip.description",
"%commands.unbanip.usage"
"%commands.unbanip.usage",
["unban-ip"]
);
$this->setPermission("pocketmine.command.unban.ip");
}

View File

@ -54,7 +54,7 @@ class Attribute{
public static function init() : void{
self::addAttribute(self::ABSORPTION, "minecraft:absorption", 0.00, 340282346638528859811704183484516925440.00, 0.00);
self::addAttribute(self::SATURATION, "minecraft:player.saturation", 0.00, 20.00, 20.00);
self::addAttribute(self::EXHAUSTION, "minecraft:player.exhaustion", 0.00, 5.00, 0.0);
self::addAttribute(self::EXHAUSTION, "minecraft:player.exhaustion", 0.00, 5.00, 0.0, false);
self::addAttribute(self::KNOCKBACK_RESISTANCE, "minecraft:knockback_resistance", 0.00, 1.00, 0.00);
self::addAttribute(self::HEALTH, "minecraft:health", 0.00, 20.00, 20.00);
self::addAttribute(self::MOVEMENT_SPEED, "minecraft:movement", 0.00, 340282346638528859811704183484516925440.00, 0.10);

View File

@ -516,7 +516,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
$this->recalculateBoundingBox();
$this->chunk = $this->level->getChunk($this->getFloorX() >> 4, $this->getFloorZ() >> 4, false);
$this->chunk = $this->level->getChunkAtPosition($this, false);
if($this->chunk === null){
throw new \InvalidStateException("Cannot create entities in unloaded chunks");
}
@ -563,7 +563,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->level->addEntity($this);
$this->lastUpdate = $this->server->getTick();
$this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this));
(new EntitySpawnEvent($this))->call();
$this->scheduleUpdate();
@ -637,6 +637,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param float $value
*/
public function setScale(float $value) : void{
if($value <= 0){
throw new \InvalidArgumentException("Scale must be greater than 0");
}
$multiplier = $value / $this->getScale();
$this->width *= $multiplier;
@ -905,7 +908,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param EntityDamageEvent $source
*/
public function attack(EntityDamageEvent $source) : void{
$this->server->getPluginManager()->callEvent($source);
$source->call();
if($source->isCancelled()){
return;
}
@ -919,7 +922,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param EntityRegainHealthEvent $source
*/
public function heal(EntityRegainHealthEvent $source) : void{
$this->server->getPluginManager()->callEvent($source);
$source->call();
if($source->isCancelled()){
return;
}
@ -1150,34 +1153,30 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
protected function broadcastMovement(bool $teleport = false) : void{
if($this->chunk !== null){
$pk = new MoveEntityAbsolutePacket();
$pk->entityRuntimeId = $this->id;
$pk->position = $this->getOffsetPosition($this);
$pk = new MoveEntityAbsolutePacket();
$pk->entityRuntimeId = $this->id;
$pk->position = $this->getOffsetPosition($this);
//this looks very odd but is correct as of 1.5.0.7
//for arrows this is actually x/y/z rotation
//for mobs x and z are used for pitch and yaw, and y is used for headyaw
$pk->xRot = $this->pitch;
$pk->yRot = $this->yaw; //TODO: head yaw
$pk->zRot = $this->yaw;
//this looks very odd but is correct as of 1.5.0.7
//for arrows this is actually x/y/z rotation
//for mobs x and z are used for pitch and yaw, and y is used for headyaw
$pk->xRot = $this->pitch;
$pk->yRot = $this->yaw; //TODO: head yaw
$pk->zRot = $this->yaw;
if($teleport){
$pk->flags |= MoveEntityAbsolutePacket::FLAG_TELEPORT;
}
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
if($teleport){
$pk->flags |= MoveEntityAbsolutePacket::FLAG_TELEPORT;
}
$this->level->broadcastPacketToViewers($this, $pk);
}
protected function broadcastMotion() : void{
if($this->chunk !== null){
$pk = new SetEntityMotionPacket();
$pk->entityRuntimeId = $this->id;
$pk->motion = $this->getMotion();
$pk = new SetEntityMotionPacket();
$pk->entityRuntimeId = $this->id;
$pk->motion = $this->getMotion();
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
}
$this->level->broadcastPacketToViewers($this, $pk);
}
protected function applyDragBeforeGravity() : bool{
@ -1808,7 +1807,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->chunk = $this->level->getChunk($chunkX, $chunkZ, true);
if(!$this->justCreated){
$newChunk = $this->level->getChunkPlayers($chunkX, $chunkZ);
$newChunk = $this->level->getViewersForPosition($this);
foreach($this->hasSpawned as $player){
if(!isset($newChunk[$player->getLoaderId()])){
$this->despawnFrom($player);
@ -1841,7 +1840,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function setMotion(Vector3 $motion) : bool{
if(!$this->justCreated){
$this->server->getPluginManager()->callEvent($ev = new EntityMotionEvent($this, $motion));
$ev = new EntityMotionEvent($this, $motion);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1874,7 +1874,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
$from = Position::fromObject($this, $this->level);
$to = Position::fromObject($pos, $pos instanceof Position ? $pos->getLevel() : $this->level);
$this->server->getPluginManager()->callEvent($ev = new EntityTeleportEvent($this, $from, $to));
$ev = new EntityTeleportEvent($this, $from, $to);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1900,7 +1901,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
if($this->isValid()){
$this->server->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel));
$ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1953,7 +1955,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param Player $player
*/
public function spawnTo(Player $player) : void{
if(!isset($this->hasSpawned[$player->getLoaderId()]) and $this->chunk !== null and isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])){
if(!isset($this->hasSpawned[$player->getLoaderId()])){
$this->hasSpawned[$player->getLoaderId()] = $player;
$this->sendSpawnPacket($player);
@ -1964,7 +1966,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
if($this->chunk === null or $this->closed){
return;
}
foreach($this->level->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){
foreach($this->level->getViewersForPosition($this) as $player){
if($player->isOnline()){
$this->spawnTo($player);
}
@ -2026,7 +2028,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
*/
public function close() : void{
if(!$this->closed){
$this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this));
(new EntityDespawnEvent($this))->call();
$this->closed = true;
$this->despawnFromAll();

View File

@ -71,8 +71,9 @@ interface EntityIds{
public const ENDER_DRAGON = 53;
public const SHULKER = 54;
public const ENDERMITE = 55;
public const LEARN_TO_CODE_MASCOT = 56;
public const AGENT = 56, LEARN_TO_CODE_MASCOT = 56;
public const VINDICATOR = 57;
public const PHANTOM = 58;
public const ARMOR_STAND = 61;
public const TRIPOD_CAMERA = 62;
@ -86,8 +87,9 @@ interface EntityIds{
public const EYE_OF_ENDER_SIGNAL = 70;
public const ENDER_CRYSTAL = 71;
public const FIREWORKS_ROCKET = 72;
public const TRIDENT = 73;
public const THROWN_TRIDENT = 73, TRIDENT = 73;
public const TURTLE = 74;
public const CAT = 75;
public const SHULKER_BULLET = 76;
public const FISHING_HOOK = 77;
public const CHALKBOARD = 78;
@ -97,7 +99,7 @@ interface EntityIds{
public const EGG = 82;
public const PAINTING = 83;
public const MINECART = 84;
public const LARGE_FIREBALL = 85;
public const FIREBALL = 85, LARGE_FIREBALL = 85;
public const SPLASH_POTION = 86;
public const ENDER_PEARL = 87;
public const LEASH_KNOT = 88;
@ -122,6 +124,7 @@ interface EntityIds{
public const PUFFERFISH = 108;
public const SALMON = 109;
public const DROWNED = 110;
public const TROPICAL_FISH = 111;
public const FISH = 112;
public const TROPICALFISH = 111, TROPICAL_FISH = 111;
public const COD = 112, FISH = 112;
public const PANDA = 113;
}

View File

@ -269,7 +269,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @return float the amount of exhaustion level increased
*/
public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{
$this->server->getPluginManager()->callEvent($ev = new PlayerExhaustEvent($this, $amount, $cause));
$ev = new PlayerExhaustEvent($this, $amount, $cause);
$ev->call();
if($ev->isCancelled()){
return 0.0;
}
@ -449,7 +450,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
private function playLevelUpSound(int $newLevel) : void{
$volume = 0x10000000 * (min(30, $newLevel) / 5); //No idea why such odd numbers, but this works...
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_LEVELUP, 1, (int) $volume);
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_LEVELUP, (int) $volume);
}
/**
@ -466,7 +467,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
protected function setXpAndProgress(?int $level, ?float $progress) : bool{
if(!$this->justCreated){
$ev = new PlayerExperienceChangeEvent($this, $this->getXpLevel(), $this->getXpProgress(), $level, $progress);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;

View File

@ -205,7 +205,8 @@ abstract class Living extends Entity implements Damageable{
if(isset($this->effects[$effectId])){
$effect = $this->effects[$effectId];
$hasExpired = $effect->hasExpired();
$this->server->getPluginManager()->callEvent($ev = new EntityEffectRemoveEvent($this, $effect));
$ev = new EntityEffectRemoveEvent($this, $effect);
$ev->call();
if($ev->isCancelled()){
if($hasExpired and !$ev->getEffect()->hasExpired()){ //altered duration of an expired effect to make it not get removed
$this->sendEffectAdd($ev->getEffect(), true);
@ -278,7 +279,7 @@ abstract class Living extends Entity implements Damageable{
$ev = new EntityEffectAddEvent($this, $effect, $oldEffect);
$ev->setCancelled($cancelled);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -622,7 +623,8 @@ abstract class Living extends Entity implements Damageable{
}
protected function onDeath() : void{
$this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops()));
$ev = new EntityDeathEvent($this, $this->getDrops());
$ev->call();
foreach($ev->getDrops() as $item){
$this->getLevel()->dropItem($this, $item);
}

View File

@ -113,7 +113,8 @@ class FallingBlock extends Entity{
//FIXME: anvils are supposed to destroy torches
$this->getLevel()->dropItem($this, ItemFactory::get($this->getBlock(), $this->getDamage()));
}else{
$this->server->getPluginManager()->callEvent($ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block));
$ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block);
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($pos, $ev->getTo(), true);
}

View File

@ -78,7 +78,7 @@ class ItemEntity extends Entity{
}
$this->server->getPluginManager()->callEvent(new ItemSpawnEvent($this));
(new ItemSpawnEvent($this))->call();
}
public function entityBaseTick(int $tickDiff = 1) : bool{
@ -96,7 +96,8 @@ class ItemEntity extends Entity{
$this->age += $tickDiff;
if($this->age > 6000){
$this->server->getPluginManager()->callEvent($ev = new ItemDespawnEvent($this));
$ev = new ItemDespawnEvent($this);
$ev->call();
if($ev->isCancelled()){
$this->age = 0;
}else{
@ -212,7 +213,8 @@ class ItemEntity extends Entity{
return;
}
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($playerInventory, $this));
$ev = new InventoryPickupItemEvent($playerInventory, $this);
$ev->call();
if($ev->isCancelled()){
return;
}

View File

@ -102,8 +102,8 @@ class PrimedTNT extends Entity implements Explosive{
}
public function explode() : void{
$this->server->getPluginManager()->callEvent($ev = new ExplosionPrimeEvent($this, 4));
$ev = new ExplosionPrimeEvent($this, 4);
$ev->call();
if(!$ev->isCancelled()){
$explosion = new Explosion($this, $ev->getForce(), $this);
if($ev->isBlockBreaking()){

View File

@ -186,7 +186,7 @@ class Arrow extends Projectile{
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return;
}

View File

@ -241,7 +241,7 @@ abstract class Projectile extends Entity{
}
if($ev !== null){
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
$this->onHit($ev);
if($ev instanceof ProjectileHitEntityEvent){
@ -315,7 +315,7 @@ abstract class Projectile extends Entity{
if($this->fireTicks > 0){
$ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$entityHit->setOnFire($ev->getDuration());
}

View File

@ -27,6 +27,9 @@ declare(strict_types=1);
namespace pocketmine\event;
abstract class Event{
private const MAX_EVENT_CALL_DEPTH = 50;
/** @var int */
private static $eventCallDepth = 1;
/** @var string|null */
protected $eventName = null;
@ -67,4 +70,37 @@ abstract class Event{
/** @var Event $this */
$this->isCancelled = $value;
}
/**
* Calls event handlers registered for this event.
*
* @throws \RuntimeException if event call recursion reaches the max depth limit
*
* @throws \ReflectionException
*/
public function call() : void{
if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
//this exception will be caught by the parent event call if all else fails
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
}
$handlerList = HandlerList::getHandlerListFor(get_class($this));
assert($handlerList !== null, "Called event should have a valid HandlerList");
++self::$eventCallDepth;
try{
foreach(EventPriority::ALL as $priority){
$currentList = $handlerList;
while($currentList !== null){
foreach($currentList->getListenersByPriority($priority) as $registration){
$registration->callEvent($this);
}
$currentList = $currentList->getParent();
}
}
}finally{
--self::$eventCallDepth;
}
}
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\inventory;
use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityArmorChangeEvent;
use pocketmine\item\Item;
use pocketmine\Server;
class ArmorInventoryEventProcessor implements InventoryEventProcessor{
/** @var Entity */
@ -37,7 +36,8 @@ class ArmorInventoryEventProcessor implements InventoryEventProcessor{
}
public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem, Item $newItem) : ?Item{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->entity, $oldItem, $newItem, $slot));
$ev = new EntityArmorChangeEvent($this->entity, $oldItem, $newItem, $slot);
$ev->call();
if($ev->isCancelled()){
return null;
}

View File

@ -399,7 +399,8 @@ abstract class BaseInventory implements Inventory{
}
public function open(Player $who) : bool{
$who->getServer()->getPluginManager()->callEvent($ev = new InventoryOpenEvent($this, $who));
$ev = new InventoryOpenEvent($this, $who);
$ev->call();
if($ev->isCancelled()){
return false;
}

View File

@ -87,6 +87,6 @@ class ChestInventory extends ContainerInventory{
$pk->z = (int) $holder->z;
$pk->eventType = 1; //it's always 1 for a chest
$pk->eventData = $isOpen ? 1 : 0;
$holder->getLevel()->addChunkPacket($holder->getFloorX() >> 4, $holder->getFloorZ() >> 4, $pk);
$holder->getLevel()->broadcastPacketToViewers($holder, $pk);
}
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\inventory;
use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityInventoryChangeEvent;
use pocketmine\item\Item;
use pocketmine\Server;
class EntityInventoryEventProcessor implements InventoryEventProcessor{
/** @var Entity */
@ -37,7 +36,8 @@ class EntityInventoryEventProcessor implements InventoryEventProcessor{
}
public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem, Item $newItem) : ?Item{
Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->entity, $oldItem, $newItem, $slot));
$ev = new EntityInventoryChangeEvent($this->entity, $oldItem, $newItem, $slot);
$ev->call();
if($ev->isCancelled()){
return null;
}

View File

@ -69,7 +69,8 @@ class PlayerInventory extends BaseInventory{
return false;
}
$this->getHolder()->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot));
$ev = new PlayerItemHeldEvent($this->getHolder(), $this->getItem($hotbarSlot), $hotbarSlot);
$ev->call();
if($ev->isCancelled()){
$this->sendHeldItem($this->getHolder());

View File

@ -134,7 +134,8 @@ class CraftingTransaction extends InventoryTransaction{
}
protected function callExecuteEvent() : bool{
$this->source->getServer()->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs));
$ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs);
$ev->call();
return !$ev->isCancelled();
}

View File

@ -29,7 +29,6 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\Player;
use pocketmine\Server;
/**
* This InventoryTransaction only allows doing Transaction between one / two inventories
@ -250,7 +249,8 @@ class InventoryTransaction{
}
protected function callExecuteEvent() : bool{
Server::getInstance()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this));
$ev = new InventoryTransactionEvent($this);
$ev->call();
return !$ev->isCancelled();
}

View File

@ -42,7 +42,8 @@ class DropItemAction extends InventoryAction{
}
public function onPreExecute(Player $source) : bool{
$source->getServer()->getPluginManager()->callEvent($ev = new PlayerDropItemEvent($source, $this->targetItem));
$ev = new PlayerDropItemEvent($source, $this->targetItem);
$ev->call();
if($ev->isCancelled()){
return false;
}

View File

@ -87,7 +87,7 @@ class Bow extends Tool{
$ev->setCancelled();
}
$player->getServer()->getPluginManager()->callEvent($ev);
$ev->call();
$entity = $ev->getProjectile(); //This might have been changed by plugins
@ -104,7 +104,8 @@ class Bow extends Tool{
}
if($entity instanceof Projectile){
$player->getServer()->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($entity));
$projectileEv = new ProjectileLaunchEvent($entity);
$projectileEv->call();
if($projectileEv->isCancelled()){
$ev->getProjectile()->flagForDespawn();
}else{

View File

@ -59,7 +59,8 @@ class Bucket extends Item implements Consumable{
$stack->pop();
$resultItem = ItemFactory::get(Item::BUCKET, $blockClicked->getFlowingForm()->getId());
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem));
$ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem);
$ev->call();
if(!$ev->isCancelled()){
$player->getLevel()->setBlock($blockClicked, BlockFactory::get(Block::AIR), true, true);
$player->getLevel()->broadcastLevelSoundEvent($blockClicked->add(0.5, 0.5, 0.5), $blockClicked->getBucketFillSound());
@ -80,7 +81,8 @@ class Bucket extends Item implements Consumable{
}
}
}elseif($resultBlock instanceof Liquid and $blockReplace->canBeReplaced()){
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, ItemFactory::get(Item::BUCKET)));
$ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, ItemFactory::get(Item::BUCKET));
$ev->call();
if(!$ev->isCancelled()){
$player->getLevel()->setBlock($blockReplace, $resultBlock->getFlowingForm(), true, true);
$player->getLevel()->broadcastLevelSoundEvent($blockClicked->add(0.5, 0.5, 0.5), $resultBlock->getBucketEmptySound());

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\EntityIds;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\math\Vector3;
@ -58,15 +59,14 @@ abstract class ProjectileItem extends Item{
$this->count--;
if($projectile instanceof Projectile){
$player->getServer()->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($projectile));
$projectileEv = new ProjectileLaunchEvent($projectile);
$projectileEv->call();
if($projectileEv->isCancelled()){
$projectile->flagForDespawn();
}else{
$projectile->spawnToAll();
//319 is the Player's entity type ID in MCPE, with all its flags (which we don't know)
//without this, it doesn't work at all.
$player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_THROW, 319);
$player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_THROW, 0, EntityIds::PLAYER);
}
}elseif($projectile !== null){
$projectile->spawnToAll();

View File

@ -153,7 +153,8 @@ class Explosion{
$yield = (1 / $this->size) * 100;
if($this->what instanceof Entity){
$this->level->getServer()->getPluginManager()->callEvent($ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield));
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield);
$ev->call();
if($ev->isCancelled()){
return false;
}else{
@ -234,7 +235,8 @@ class Explosion{
continue;
}
if(!isset($this->affectedBlocks[$index = Level::blockHash($sideBlock->x, $sideBlock->y, $sideBlock->z)]) and !isset($updateBlocks[$index])){
$this->level->getServer()->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->level->getBlockAt($sideBlock->x, $sideBlock->y, $sideBlock->z)));
$ev = new BlockUpdateEvent($this->level->getBlockAt($sideBlock->x, $sideBlock->y, $sideBlock->z));
$ev->call();
if(!$ev->isCancelled()){
foreach($this->level->getNearbyEntities(new AxisAlignedBB($sideBlock->x - 1, $sideBlock->y - 1, $sideBlock->z - 1, $sideBlock->x + 2, $sideBlock->y + 2, $sideBlock->z + 2)) as $entity){
$entity->onNearbyBlockChange();
@ -251,7 +253,7 @@ class Explosion{
$pk->position = $this->source->asVector3();
$pk->radius = $this->size;
$pk->records = $send;
$this->level->addChunkPacket($source->getFloorX() >> 4, $source->getFloorZ() >> 4, $pk);
$this->level->broadcastPacketToViewers($source, $pk);
$this->level->addParticle(new HugeExplodeSeedParticle($source));
$this->level->broadcastLevelSoundEvent($source, LevelSoundEventPacket::SOUND_EXPLODE);

View File

@ -69,6 +69,7 @@ use pocketmine\metadata\Metadatable;
use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
@ -241,12 +242,20 @@ class Level implements ChunkManager, Metadatable{
/** @var int */
public $tickRateCounter = 0;
/** @var bool */
private $doingTick = false;
/** @var string|Generator */
private $generator;
/** @var bool */
private $closed = false;
/** @var BlockLightUpdate|null */
private $blockLightUpdate = null;
/** @var SkyLightUpdate|null */
private $skyLightUpdate = null;
public static function chunkHash(int $x, int $z) : int{
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF);
}
@ -442,13 +451,14 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if($players === null){
foreach($pk as $e){
$this->addChunkPacket($sound->getFloorX() >> 4, $sound->getFloorZ() >> 4, $e);
if(!empty($pk)){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($sound, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}
@ -457,13 +467,14 @@ class Level implements ChunkManager, Metadatable{
if(!is_array($pk)){
$pk = [$pk];
}
if($players === null){
foreach($pk as $e){
$this->addChunkPacket($particle->getFloorX() >> 4, $particle->getFloorZ() >> 4, $e);
if(!empty($pk)){
if($players === null){
foreach($pk as $e){
$this->broadcastPacketToViewers($particle, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}else{
$this->server->batchPackets($players, $pk, false);
}
}
@ -480,10 +491,10 @@ class Level implements ChunkManager, Metadatable{
$pk->data = $data;
if($pos !== null){
$pk->position = $pos->asVector3();
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $pk);
$this->broadcastPacketToViewers($pos, $pk);
}else{
$pk->position = null;
$this->addGlobalPacket($pk);
$this->broadcastGlobalPacket($pk);
}
}
@ -492,20 +503,20 @@ class Level implements ChunkManager, Metadatable{
*
* @param Vector3 $pos
* @param int $soundId
* @param int $pitch
* @param int $extraData
* @param int $entityTypeId
* @param bool $isBabyMob
* @param bool $disableRelativeVolume If true, all players receiving this sound-event will hear the sound at full volume regardless of distance
*/
public function broadcastLevelSoundEvent(Vector3 $pos, int $soundId, int $pitch = 1, int $extraData = -1, bool $isBabyMob = false, bool $disableRelativeVolume = false){
public function broadcastLevelSoundEvent(Vector3 $pos, int $soundId, int $extraData = -1, int $entityTypeId = -1, bool $isBabyMob = false, bool $disableRelativeVolume = false){
$pk = new LevelSoundEventPacket();
$pk->sound = $soundId;
$pk->pitch = $pitch;
$pk->extraData = $extraData;
$pk->entityType = AddEntityPacket::LEGACY_ID_MAP_BC[$entityTypeId] ?? ":";
$pk->isBabyMob = $isBabyMob;
$pk->disableRelativeVolume = $disableRelativeVolume;
$pk->position = $pos->asVector3();
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $pk);
$this->broadcastPacketToViewers($pos, $pk);
}
public function getAutoSave() : bool{
@ -524,8 +535,12 @@ class Level implements ChunkManager, Metadatable{
* @param bool $force default false, force unload of default level
*
* @return bool
* @throws \InvalidStateException if trying to unload a level during level tick
*/
public function unload(bool $force = false) : bool{
if($this->doingTick and !$force){
throw new \InvalidStateException("Cannot unload a level during level tick");
}
$ev = new LevelUnloadEvent($this);
@ -533,7 +548,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(true);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$force and $ev->isCancelled()){
return false;
@ -584,6 +599,16 @@ class Level implements ChunkManager, Metadatable{
return $this->chunkLoaders[Level::chunkHash($chunkX, $chunkZ)] ?? [];
}
/**
* Returns an array of players who have the target position within their view distance.
* @param Vector3 $pos
*
* @return Player[]
*/
public function getViewersForPosition(Vector3 $pos) : array{
return $this->getChunkPlayers($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4);
}
/**
* Queues a DataPacket to be sent to all players using the chunk at the specified X/Z coordinates at the end of the
* current tick.
@ -601,7 +626,27 @@ class Level implements ChunkManager, Metadatable{
}
/**
* Queues a DataPacket to be sent to everyone in the Level at the end of the current tick.
* Broadcasts a packet to every player who has the target position within their view distance.
*
* @param Vector3 $pos
* @param DataPacket $packet
*/
public function broadcastPacketToViewers(Vector3 $pos, DataPacket $packet) : void{
$this->addChunkPacket($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $packet);
}
/**
* Broadcasts a packet to every player in the level.
*
* @param DataPacket $packet
*/
public function broadcastGlobalPacket(DataPacket $packet) : void{
$this->globalPackets[] = $packet;
}
/**
* @deprecated
* @see Level::broadcastGlobalPacket()
*
* @param DataPacket $packet
*/
@ -693,7 +738,16 @@ class Level implements ChunkManager, Metadatable{
}
$this->timings->doTick->startTiming();
$this->doingTick = true;
try{
$this->actuallyDoTick($currentTick);
}finally{
$this->doingTick = false;
$this->timings->doTick->stopTiming();
}
}
protected function actuallyDoTick(int $currentTick) : void{
$this->checkTime();
$this->sunAnglePercentage = $this->computeSunAnglePercentage(); //Sun angle depends on the current time
@ -728,7 +782,8 @@ class Level implements ChunkManager, Metadatable{
$block = $this->getBlockAt($x, $y, $z);
$block->clearCaches(); //for blocks like fences, force recalculation of connected AABBs
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block));
$ev = new BlockUpdateEvent($block);
$ev->call();
if(!$ev->isCancelled()){
$block->onNearbyBlockChange();
}
@ -765,6 +820,8 @@ class Level implements ChunkManager, Metadatable{
$this->tickChunks();
$this->timings->doTickTiles->stopTiming();
$this->executeQueuedLightUpdates();
if(count($this->changedBlocks) > 0){
if(count($this->players) > 0){
foreach($this->changedBlocks as $index => $blocks){
@ -812,8 +869,6 @@ class Level implements ChunkManager, Metadatable{
}
$this->chunkPackets = [];
$this->timings->doTick->stopTiming();
}
public function checkSleep(){
@ -1038,7 +1093,7 @@ class Level implements ChunkManager, Metadatable{
return false;
}
$this->server->getPluginManager()->callEvent(new LevelSaveEvent($this));
(new LevelSaveEvent($this))->call();
$this->provider->setTime($this->time);
$this->saveChunks();
@ -1419,22 +1474,21 @@ class Level implements ChunkManager, Metadatable{
$newHeightMap = $oldHeightMap;
}
$update = new SkyLightUpdate($this);
if($this->skyLightUpdate === null){
$this->skyLightUpdate = new SkyLightUpdate($this);
}
if($newHeightMap > $oldHeightMap){ //Heightmap increase, block placed, remove sky light
for($i = $y; $i >= $oldHeightMap; --$i){
$update->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest.
$this->skyLightUpdate->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest.
}
}elseif($newHeightMap < $oldHeightMap){ //Heightmap decrease, block changed or removed, add sky light
for($i = $y; $i >= $newHeightMap; --$i){
$update->setAndUpdateLight($x, $i, $z, 15);
$this->skyLightUpdate->setAndUpdateLight($x, $i, $z, 15);
}
}else{ //No heightmap change, block changed "underground"
$update->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockSkyLight($x, $y, $z) - BlockFactory::$lightFilter[$sourceId]));
$this->skyLightUpdate->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockSkyLight($x, $y, $z) - BlockFactory::$lightFilter[$sourceId]));
}
$update->execute();
$this->timings->doBlockSkyLightUpdates->stopTiming();
}
@ -1464,13 +1518,30 @@ class Level implements ChunkManager, Metadatable{
$id = $this->getBlockIdAt($x, $y, $z);
$newLevel = max(BlockFactory::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - BlockFactory::$lightFilter[$id]);
$update = new BlockLightUpdate($this);
$update->setAndUpdateLight($x, $y, $z, $newLevel);
$update->execute();
if($this->blockLightUpdate === null){
$this->blockLightUpdate = new BlockLightUpdate($this);
}
$this->blockLightUpdate->setAndUpdateLight($x, $y, $z, $newLevel);
$this->timings->doBlockLightUpdates->stopTiming();
}
public function executeQueuedLightUpdates() : void{
if($this->blockLightUpdate !== null){
$this->timings->doBlockLightUpdates->startTiming();
$this->blockLightUpdate->execute();
$this->blockLightUpdate = null;
$this->timings->doBlockLightUpdates->stopTiming();
}
if($this->skyLightUpdate !== null){
$this->timings->doBlockSkyLightUpdates->startTiming();
$this->skyLightUpdate->execute();
$this->skyLightUpdate = null;
$this->timings->doBlockSkyLightUpdates->stopTiming();
}
}
/**
* Sets on Vector3 the data from a Block object,
* does block updates and puts the changes to the send queue.
@ -1497,7 +1568,7 @@ class Level implements ChunkManager, Metadatable{
$this->timings->setBlock->startTiming();
if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
if($this->getChunkAtPosition($pos, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
if(!($pos instanceof Position)){
$pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
}
@ -1530,7 +1601,8 @@ class Level implements ChunkManager, Metadatable{
if($update){
$this->updateAllLight($block);
$this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block));
$ev = new BlockUpdateEvent($block);
$ev->call();
if(!$ev->isCancelled()){
foreach($this->getNearbyEntities(new AxisAlignedBB($block->x - 1, $block->y - 1, $block->z - 1, $block->x + 2, $block->y + 2, $block->z + 2)) as $entity){
$entity->onNearbyBlockChange();
@ -1693,7 +1765,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(!$canBreak);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1783,7 +1855,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(); //set it to cancelled so plugins can bypass this
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
if(!$player->isSneaking() and $blockClicked->onActivate($item, $player)){
return true;
@ -1855,7 +1927,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(!$canPlace);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1866,7 +1938,7 @@ class Level implements ChunkManager, Metadatable{
}
if($playSound){
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, $hand->getRuntimeId());
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, $hand->getRuntimeId());
}
$item->pop();
@ -2108,6 +2180,9 @@ class Level implements ChunkManager, Metadatable{
* @param int $id 0-255
*/
public function setBlockIdAt(int $x, int $y, int $z, int $id){
if(!$this->isInWorld($x, $y, $z)){ //TODO: bad hack but fixing this requires BC breaks to do properly :(
return;
}
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
@ -2142,6 +2217,9 @@ class Level implements ChunkManager, Metadatable{
* @param int $data 0-15
*/
public function setBlockDataAt(int $x, int $y, int $z, int $data){
if(!$this->isInWorld($x, $y, $z)){ //TODO: bad hack but fixing this requires BC breaks to do properly :(
return;
}
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
@ -2280,6 +2358,18 @@ class Level implements ChunkManager, Metadatable{
return null;
}
/**
* Returns the chunk containing the given Vector3 position.
*
* @param Vector3 $pos
* @param bool $create
*
* @return null|Chunk
*/
public function getChunkAtPosition(Vector3 $pos, bool $create = false) : ?Chunk{
return $this->getChunk($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4, $create);
}
/**
* Returns the chunks adjacent to the specified chunk.
*
@ -2317,7 +2407,7 @@ class Level implements ChunkManager, Metadatable{
$oldChunk = $this->getChunk($x, $z, false);
$this->setChunk($x, $z, $chunk, false);
if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){
$this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($this, $chunk));
(new ChunkPopulateEvent($this, $chunk))->call();
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkPopulated($chunk);
@ -2396,6 +2486,17 @@ class Level implements ChunkManager, Metadatable{
return $this->getChunk($x >> 4, $z >> 4, true)->getHighestBlockAt($x & 0x0f, $z & 0x0f);
}
/**
* Returns whether the given position is in a loaded area of terrain.
*
* @param Vector3 $pos
*
* @return bool
*/
public function isInLoadedTerrain(Vector3 $pos) : bool{
return $this->isChunkLoaded($pos->getFloorX() >> 4, $pos->getFloorZ() >> 4);
}
/**
* @param int $x
* @param int $z
@ -2445,7 +2546,7 @@ class Level implements ChunkManager, Metadatable{
public function setSpawnLocation(Vector3 $pos){
$previousSpawn = $this->getSpawnLocation();
$this->provider->setSpawn($pos);
$this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn));
(new SpawnChangeEvent($this, $previousSpawn))->call();
}
public function requestChunk(int $x, int $z, Player $player){
@ -2667,7 +2768,7 @@ class Level implements ChunkManager, Metadatable{
$chunk->initChunk($this);
$this->server->getPluginManager()->callEvent(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated()));
(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated()))->call();
if(!$chunk->isLightPopulated() and $chunk->isPopulated() and $this->getServer()->getProperty("chunk-ticking.light-updates", false)){
$this->getServer()->getAsyncPool()->submitTask(new LightPopulationTask($this, $chunk));
@ -2721,7 +2822,8 @@ class Level implements ChunkManager, Metadatable{
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
$this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($this, $chunk));
$ev = new ChunkUnloadEvent($this, $chunk);
$ev->call();
if($ev->isCancelled()){
$this->timings->doChunkUnload->stopTiming();
@ -2788,7 +2890,7 @@ class Level implements ChunkManager, Metadatable{
$max = $this->worldHeight;
$v = $spawn->floor();
$chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false);
$chunk = $this->getChunkAtPosition($v, false);
$x = (int) $v->x;
$z = (int) $v->z;
if($chunk !== null and $chunk->isGenerated()){

View File

@ -840,8 +840,12 @@ class Chunk{
*/
public function collectGarbage() : void{
foreach($this->subChunks as $y => $subChunk){
if(!($subChunk instanceof EmptySubChunk) and $subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
$this->subChunks[$y] = $this->emptySubChunk;
if($subChunk instanceof SubChunk){
if($subChunk->isEmpty()){
$this->subChunks[$y] = $this->emptySubChunk;
}else{
$subChunk->collectGarbage();
}
}
}
}

View File

@ -23,8 +23,11 @@ declare(strict_types=1);
namespace pocketmine\level\format;
class SubChunk implements SubChunkInterface{
if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
define(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY', str_repeat("\x00", 2048));
}
class SubChunk implements SubChunkInterface{
protected $ids;
protected $data;
protected $blockLight;
@ -44,6 +47,7 @@ class SubChunk implements SubChunkInterface{
self::assignData($this->data, $data, 2048);
self::assignData($this->skyLight, $skyLight, 2048, "\xff");
self::assignData($this->blockLight, $blockLight, 2048);
$this->collectGarbage();
}
public function isEmpty(bool $checkLight = true) : bool{
@ -51,7 +55,7 @@ class SubChunk implements SubChunkInterface{
substr_count($this->ids, "\x00") === 4096 and
(!$checkLight or (
substr_count($this->skyLight, "\xff") === 2048 and
substr_count($this->blockLight, "\x00") === 2048
$this->blockLight === ZERO_NIBBLE_ARRAY
))
);
}
@ -66,31 +70,22 @@ class SubChunk implements SubChunkInterface{
}
public function getBlockData(int $x, int $y, int $z) : int{
$m = ord($this->data{($x << 7) + ($z << 3) + ($y >> 1)});
if(($y & 1) === 0){
return $m & 0x0f;
}else{
return $m >> 4;
}
return (ord($this->data{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
}
public function setBlockData(int $x, int $y, int $z, int $data) : bool{
$i = ($x << 7) | ($z << 3) | ($y >> 1);
if(($y & 1) === 0){
$this->data{$i} = chr((ord($this->data{$i}) & 0xf0) | ($data & 0x0f));
}else{
$this->data{$i} = chr((($data & 0x0f) << 4) | (ord($this->data{$i}) & 0x0f));
}
$shift = ($y & 1) << 2;
$byte = ord($this->data{$i});
$this->data{$i} = chr(($byte & ~(0xf << $shift)) | (($data & 0xf) << $shift));
return true;
}
public function getFullBlock(int $x, int $y, int $z) : int{
$i = ($x << 8) | ($z << 4) | $y;
if(($y & 1) === 0){
return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0f);
}else{
return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4);
}
return (ord($this->ids{$i}) << 4) | ((ord($this->data{$i >> 1}) >> (($y & 1) << 2)) & 0xf);
}
public function setBlock(int $x, int $y, int $z, ?int $id = null, ?int $data = null) : bool{
@ -106,12 +101,10 @@ class SubChunk implements SubChunkInterface{
if($data !== null){
$i >>= 1;
$shift = ($y & 1) << 2;
$oldPair = ord($this->data{$i});
if(($y & 1) === 0){
$newPair = ($oldPair & 0xf0) | ($data & 0x0f);
}else{
$newPair = (($data & 0x0f) << 4) | ($oldPair & 0x0f);
}
$newPair = ($oldPair & ~(0xf << $shift)) | (($data & 0xf) << $shift);
if($newPair !== $oldPair){
$this->data{$i} = chr($newPair);
$changed = true;
@ -122,42 +115,30 @@ class SubChunk implements SubChunkInterface{
}
public function getBlockLight(int $x, int $y, int $z) : int{
$byte = ord($this->blockLight{($x << 7) + ($z << 3) + ($y >> 1)});
if(($y & 1) === 0){
return $byte & 0x0f;
}else{
return $byte >> 4;
}
return (ord($this->blockLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
}
public function setBlockLight(int $x, int $y, int $z, int $level) : bool{
$i = ($x << 7) + ($z << 3) + ($y >> 1);
$i = ($x << 7) | ($z << 3) | ($y >> 1);
$shift = ($y & 1) << 2;
$byte = ord($this->blockLight{$i});
if(($y & 1) === 0){
$this->blockLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f));
}else{
$this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f));
}
$this->blockLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
return true;
}
public function getBlockSkyLight(int $x, int $y, int $z) : int{
$byte = ord($this->skyLight{($x << 7) + ($z << 3) + ($y >> 1)});
if(($y & 1) === 0){
return $byte & 0x0f;
}else{
return $byte >> 4;
}
return (ord($this->skyLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
}
public function setBlockSkyLight(int $x, int $y, int $z, int $level) : bool{
$i = ($x << 7) + ($z << 3) + ($y >> 1);
$i = ($x << 7) | ($z << 3) | ($y >> 1);
$shift = ($y & 1) << 2;
$byte = ord($this->skyLight{$i});
if(($y & 1) === 0){
$this->skyLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f));
}else{
$this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f));
}
$this->skyLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
return true;
}
@ -226,4 +207,21 @@ class SubChunk implements SubChunkInterface{
public function __debugInfo(){
return [];
}
public function collectGarbage() : void{
/*
* This strange looking code is designed to exploit PHP's copy-on-write behaviour. Assigning will copy a
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
* modified, which is perfect for this purpose.
*/
if($this->data === ZERO_NIBBLE_ARRAY){
$this->data = ZERO_NIBBLE_ARRAY;
}
if($this->skyLight === ZERO_NIBBLE_ARRAY){
$this->skyLight = ZERO_NIBBLE_ARRAY;
}
if($this->blockLight === ZERO_NIBBLE_ARRAY){
$this->blockLight = ZERO_NIBBLE_ARRAY;
}
}
}

View File

@ -60,7 +60,7 @@ class ChunkRequestTask extends AsyncTask{
$batch->setCompressionLevel($this->compressionLevel);
$batch->encode();
$this->setResult($batch->buffer, false);
$this->setResult($batch->buffer);
}
public function onCompletion(Server $server){

View File

@ -33,10 +33,8 @@ abstract class Tree{
public $overridable = [
Block::AIR => true,
Block::SAPLING => true,
Block::LOG => true,
Block::LEAVES => true,
Block::SNOW_LAYER => true,
Block::LOG2 => true,
Block::LEAVES2 => true
];

View File

@ -34,6 +34,9 @@ abstract class LightUpdate{
/** @var ChunkManager */
protected $level;
/** @var int[] blockhash => new light level */
protected $updateNodes = [];
/** @var \SplQueue */
protected $spreadQueue;
/** @var bool[] */
@ -59,31 +62,31 @@ abstract class LightUpdate{
abstract protected function setLight(int $x, int $y, int $z, int $level);
public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){
if(!$this->level->isInWorld($x, $y, $z)){
throw new \InvalidArgumentException("Coordinates x=$x, y=$y, z=$z are out of range");
}
$this->updateNodes[Level::blockHash($x, $y, $z)] = [$x, $y, $z, $newLevel];
}
if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){
throw new \InvalidArgumentException("Already have a visit ready for this block");
}
private function prepareNodes() : void{
foreach($this->updateNodes as $blockHash => [$x, $y, $z, $newLevel]){
if($this->subChunkHandler->moveTo($x, $y, $z)){
$oldLevel = $this->getLight($x, $y, $z);
if($this->subChunkHandler->moveTo($x, $y, $z)){
$oldLevel = $this->getLight($x, $y, $z);
if($oldLevel !== $newLevel){
$this->setLight($x, $y, $z, $newLevel);
if($oldLevel < $newLevel){ //light increased
$this->spreadVisited[$index] = true;
$this->spreadQueue->enqueue([$x, $y, $z]);
}else{ //light removed
$this->removalVisited[$index] = true;
$this->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
if($oldLevel !== $newLevel){
$this->setLight($x, $y, $z, $newLevel);
if($oldLevel < $newLevel){ //light increased
$this->spreadVisited[$blockHash] = true;
$this->spreadQueue->enqueue([$x, $y, $z]);
}else{ //light removed
$this->removalVisited[$blockHash] = true;
$this->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
}
}
}
}
}
public function execute(){
$this->prepareNodes();
while(!$this->removalQueue->isEmpty()){
list($x, $y, $z, $oldAdjacentLight) = $this->removalQueue->dequeue();

View File

@ -35,55 +35,26 @@ class BlockMetadataStore extends MetadataStore{
$this->owningLevel = $owningLevel;
}
public function disambiguate(Metadatable $block, string $metadataKey) : string{
if(!($block instanceof Block)){
throw new \InvalidArgumentException("Argument must be a Block instance");
private function disambiguate(Block $block, string $metadataKey) : string{
if($block->getLevel() !== $this->owningLevel){
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
}
return $block->x . ":" . $block->y . ":" . $block->z . ":" . $metadataKey;
}
public function getMetadata(Metadatable $subject, string $metadataKey){
if(!($subject instanceof Block)){
throw new \InvalidArgumentException("Object must be a Block");
}
if($subject->getLevel() === $this->owningLevel){
return parent::getMetadata($subject, $metadataKey);
}else{
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
}
public function getMetadata(Block $subject, string $metadataKey){
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function hasMetadata(Metadatable $subject, string $metadataKey) : bool{
if(!($subject instanceof Block)){
throw new \InvalidArgumentException("Object must be a Block");
}
if($subject->getLevel() === $this->owningLevel){
return parent::hasMetadata($subject, $metadataKey);
}else{
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
}
public function hasMetadata(Block $subject, string $metadataKey) : bool{
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function removeMetadata(Metadatable $subject, string $metadataKey, Plugin $owningPlugin){
if(!($subject instanceof Block)){
throw new \InvalidArgumentException("Object must be a Block");
}
if($subject->getLevel() === $this->owningLevel){
parent::removeMetadata($subject, $metadataKey, $owningPlugin);
}else{
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
}
public function removeMetadata(Block $subject, string $metadataKey, Plugin $owningPlugin){
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
}
public function setMetadata(Metadatable $subject, string $metadataKey, MetadataValue $newMetadataValue){
if(!($subject instanceof Block)){
throw new \InvalidArgumentException("Object must be a Block");
}
if($subject->getLevel() === $this->owningLevel){
parent::setMetadata($subject, $metadataKey, $newMetadataValue);
}else{
throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName());
}
public function setMetadata(Block $subject, string $metadataKey, MetadataValue $newMetadataValue){
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
}
}

View File

@ -24,14 +24,27 @@ declare(strict_types=1);
namespace pocketmine\metadata;
use pocketmine\entity\Entity;
use pocketmine\plugin\Plugin;
class EntityMetadataStore extends MetadataStore{
public function disambiguate(Metadatable $entity, string $metadataKey) : string{
if(!($entity instanceof Entity)){
throw new \InvalidArgumentException("Argument must be an Entity instance");
}
private function disambiguate(Entity $entity, string $metadataKey) : string{
return $entity->getId() . ":" . $metadataKey;
}
public function getMetadata(Entity $subject, string $metadataKey){
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function hasMetadata(Entity $subject, string $metadataKey) : bool{
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function removeMetadata(Entity $subject, string $metadataKey, Plugin $owningPlugin){
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
}
public function setMetadata(Entity $subject, string $metadataKey, MetadataValue $newMetadataValue){
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
}
}

View File

@ -24,14 +24,27 @@ declare(strict_types=1);
namespace pocketmine\metadata;
use pocketmine\level\Level;
use pocketmine\plugin\Plugin;
class LevelMetadataStore extends MetadataStore{
public function disambiguate(Metadatable $level, string $metadataKey) : string{
if(!($level instanceof Level)){
throw new \InvalidArgumentException("Argument must be a Level instance");
}
private function disambiguate(Level $level, string $metadataKey) : string{
return strtolower($level->getName()) . ":" . $metadataKey;
}
public function getMetadata(Level $subject, string $metadataKey){
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function hasMetadata(Level $subject, string $metadataKey) : bool{
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function removeMetadata(Level $subject, string $metadataKey, Plugin $owningPlugin){
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
}
public function setMetadata(Level $subject, string $metadataKey, MetadataValue $newMetadataValue){
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
}
}

View File

@ -27,7 +27,6 @@ declare(strict_types=1);
namespace pocketmine\metadata;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException;
abstract class MetadataStore{
/** @var \SplObjectStorage[] */
@ -36,17 +35,12 @@ abstract class MetadataStore{
/**
* Adds a metadata value to an object.
*
* @param Metadatable $subject
* @param string $metadataKey
* @param string $key
* @param MetadataValue $newMetadataValue
*/
public function setMetadata(Metadatable $subject, string $metadataKey, MetadataValue $newMetadataValue){
protected function setMetadataInternal(string $key, MetadataValue $newMetadataValue){
$owningPlugin = $newMetadataValue->getOwningPlugin();
if($owningPlugin === null){
throw new PluginException("Plugin cannot be null");
}
$key = $this->disambiguate($subject, $metadataKey);
if(!isset($this->metadataMap[$key])){
$entry = new \SplObjectStorage();
$this->metadataMap[$key] = $entry;
@ -60,13 +54,11 @@ abstract class MetadataStore{
* Returns all metadata values attached to an object. If multiple
* have attached metadata, each will value will be included.
*
* @param Metadatable $subject
* @param string $metadataKey
* @param string $key
*
* @return MetadataValue[]
*/
public function getMetadata(Metadatable $subject, string $metadataKey){
$key = $this->disambiguate($subject, $metadataKey);
protected function getMetadataInternal(string $key){
if(isset($this->metadataMap[$key])){
return $this->metadataMap[$key];
}else{
@ -77,24 +69,21 @@ abstract class MetadataStore{
/**
* Tests to see if a metadata attribute has been set on an object.
*
* @param Metadatable $subject
* @param string $metadataKey
* @param string $key
*
* @return bool
*/
public function hasMetadata(Metadatable $subject, string $metadataKey) : bool{
return isset($this->metadataMap[$this->disambiguate($subject, $metadataKey)]);
protected function hasMetadataInternal(string $key) : bool{
return isset($this->metadataMap[$key]);
}
/**
* Removes a metadata item owned by a plugin from a subject.
*
* @param Metadatable $subject
* @param string $metadataKey
* @param Plugin $owningPlugin
* @param string $key
* @param Plugin $owningPlugin
*/
public function removeMetadata(Metadatable $subject, string $metadataKey, Plugin $owningPlugin){
$key = $this->disambiguate($subject, $metadataKey);
protected function removeMetadataInternal(string $key, Plugin $owningPlugin){
if(isset($this->metadataMap[$key])){
unset($this->metadataMap[$key][$owningPlugin]);
if($this->metadataMap[$key]->count() === 0){
@ -118,17 +107,4 @@ abstract class MetadataStore{
}
}
}
/**
* Creates a unique name for the object receiving metadata by combining
* unique data from the subject with a metadataKey.
*
* @param Metadatable $subject
* @param string $metadataKey
*
* @return string
*
* @throws \InvalidArgumentException
*/
abstract public function disambiguate(Metadatable $subject, string $metadataKey) : string;
}

View File

@ -24,14 +24,27 @@ declare(strict_types=1);
namespace pocketmine\metadata;
use pocketmine\IPlayer;
use pocketmine\plugin\Plugin;
class PlayerMetadataStore extends MetadataStore{
public function disambiguate(Metadatable $player, string $metadataKey) : string{
if(!($player instanceof IPlayer)){
throw new \InvalidArgumentException("Argument must be an IPlayer instance");
}
private function disambiguate(IPlayer $player, string $metadataKey) : string{
return strtolower($player->getName()) . ":" . $metadataKey;
}
public function getMetadata(IPlayer $subject, string $metadataKey){
return $this->getMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function hasMetadata(IPlayer $subject, string $metadataKey) : bool{
return $this->hasMetadataInternal($this->disambiguate($subject, $metadataKey));
}
public function removeMetadata(IPlayer $subject, string $metadataKey, Plugin $owningPlugin){
$this->removeMetadataInternal($this->disambiguate($subject, $metadataKey), $owningPlugin);
}
public function setMetadata(IPlayer $subject, string $metadataKey, MetadataValue $newMetadataValue){
$this->setMetadataInternal($this->disambiguate($subject, $metadataKey), $newMetadataValue);
}
}

View File

@ -51,7 +51,7 @@ class CompressBatchedTask extends AsyncTask{
$batch->setCompressionLevel($this->level);
$batch->encode();
$this->setResult($batch->buffer, false);
$this->setResult($batch->buffer);
}
public function onCompletion(Server $server){

View File

@ -98,7 +98,7 @@ class Network{
$logger->logException($e);
}
$this->server->getPluginManager()->callEvent(new NetworkInterfaceCrashEvent($interface, $e));
(new NetworkInterfaceCrashEvent($interface, $e))->call();
$interface->emergencyShutdown();
$this->unregisterInterface($interface);
@ -110,7 +110,8 @@ class Network{
* @param SourceInterface $interface
*/
public function registerInterface(SourceInterface $interface){
$this->server->getPluginManager()->callEvent($ev = new NetworkInterfaceRegisterEvent($interface));
$ev = new NetworkInterfaceRegisterEvent($interface);
$ev->call();
if(!$ev->isCancelled()){
$interface->start();
$this->interfaces[$hash = spl_object_hash($interface)] = $interface;
@ -126,7 +127,7 @@ class Network{
* @param SourceInterface $interface
*/
public function unregisterInterface(SourceInterface $interface){
$this->server->getPluginManager()->callEvent(new NetworkInterfaceUnregisterEvent($interface));
(new NetworkInterfaceUnregisterEvent($interface))->call();
unset($this->interfaces[$hash = spl_object_hash($interface)], $this->advancedInterfaces[$hash]);
}

View File

@ -32,6 +32,8 @@ use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
@ -69,6 +71,7 @@ use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -79,6 +82,7 @@ use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveEntityDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
@ -126,6 +130,7 @@ use pocketmine\network\mcpe\protocol\ShowProfilePacket;
use pocketmine\network\mcpe\protocol\ShowStoreOfferPacket;
use pocketmine\network\mcpe\protocol\SimpleEventPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\SpawnParticleEffectPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\StopSoundPacket;
use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket;
@ -237,7 +242,7 @@ abstract class NetworkSession{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
return false;
}
@ -608,4 +613,25 @@ abstract class NetworkSession{
public function handleScriptCustomEvent(ScriptCustomEventPacket $packet) : bool{
return false;
}
public function handleSpawnParticleEffect(SpawnParticleEffectPacket $packet) : bool{
return false;
}
public function handleAvailableEntityIdentifiers(AvailableEntityIdentifiersPacket $packet) : bool{
return false;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return false;
}
public function handleNetworkChunkPublisherUpdate(NetworkChunkPublisherUpdatePacket $packet) : bool{
return false;
}
public function handleBiomeDefinitionList(BiomeDefinitionListPacket $packet) : bool{
return false;
}
}

View File

@ -44,6 +44,7 @@ use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
@ -58,6 +59,7 @@ use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
@ -92,7 +94,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
$this->server->getLogger()->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": 0x" . bin2hex($remains));
}
$this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this->player, $packet));
$ev = new DataPacketReceiveEvent($this->player, $packet);
$ev->call();
if(!$ev->isCancelled() and !$packet->handle($this)){
$this->server->getLogger()->debug("Unhandled " . $packet->getName() . " received from " . $this->player->getName() . ": 0x" . bin2hex($packet->buffer));
}
@ -124,8 +127,8 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handleMovePlayer($packet);
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
return true; //useless leftover from 1.8
}
public function handleEntityEvent(EntityEventPacket $packet) : bool{
@ -279,4 +282,13 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false; //TODO: GUI stuff
}
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
$this->player->doFirstSpawn();
return true;
}
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
return $this->player->handleLevelSoundEvent($packet);
}
}

View File

@ -91,7 +91,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
$this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
$this->server->getNetwork()->processInterface($this);
});
$this->rakLib->start(PTHREADS_INHERIT_CONSTANTS | PTHREADS_INHERIT_INI); //HACK: MainLogger needs INI and constants
$this->rakLib->start(PTHREADS_INHERIT_CONSTANTS); //HACK: MainLogger needs constants for exception logging
}
public function setNetwork(Network $network){
@ -137,7 +137,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
public function openSession(string $identifier, string $address, int $port, int $clientID) : void{
$ev = new PlayerCreationEvent($this, Player::class, Player::class, $address, $port);
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
$class = $ev->getPlayerClass();
/**
@ -154,17 +154,19 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
public function handleEncapsulated(string $identifier, EncapsulatedPacket $packet, int $flags) : void{
if(isset($this->players[$identifier])){
//get this now for blocking in case the player was closed before the exception was raised
$address = $this->players[$identifier]->getAddress();
$player = $this->players[$identifier];
$address = $player->getAddress();
try{
if($packet->buffer !== ""){
$pk = PacketPool::getPacket($packet->buffer);
$this->players[$identifier]->handleDataPacket($pk);
$player->handleDataPacket($pk);
}
}catch(\Throwable $e){
$logger = $this->server->getLogger();
$logger->debug("Packet " . (isset($pk) ? get_class($pk) : "unknown") . " 0x" . bin2hex($packet->buffer));
$logger->logException($e);
$player->close($player->getLeaveMessage(), "Internal server error");
$this->interface->blockAddress($address, 5);
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\entity\Attribute;
use pocketmine\entity\EntityIds;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\EntityLink;
@ -33,6 +34,114 @@ use pocketmine\network\mcpe\protocol\types\EntityLink;
class AddEntityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ADD_ENTITY_PACKET;
/*
* Really really really really really nasty hack, to preserve backwards compatibility.
* We can't transition to string IDs within 3.x because the network IDs (the integer ones) are exposed
* to the API in some places (for god's sake shoghi).
*
* TODO: remove this on 4.0
*/
public const LEGACY_ID_MAP_BC = [
EntityIds::NPC => "minecraft:npc",
EntityIds::PLAYER => "minecraft:player",
EntityIds::WITHER_SKELETON => "minecraft:wither_skeleton",
EntityIds::HUSK => "minecraft:husk",
EntityIds::STRAY => "minecraft:stray",
EntityIds::WITCH => "minecraft:witch",
EntityIds::ZOMBIE_VILLAGER => "minecraft:zombie_villager",
EntityIds::BLAZE => "minecraft:blaze",
EntityIds::MAGMA_CUBE => "minecraft:magma_cube",
EntityIds::GHAST => "minecraft:ghast",
EntityIds::CAVE_SPIDER => "minecraft:cave_spider",
EntityIds::SILVERFISH => "minecraft:silverfish",
EntityIds::ENDERMAN => "minecraft:enderman",
EntityIds::SLIME => "minecraft:slime",
EntityIds::ZOMBIE_PIGMAN => "minecraft:zombie_pigman",
EntityIds::SPIDER => "minecraft:spider",
EntityIds::SKELETON => "minecraft:skeleton",
EntityIds::CREEPER => "minecraft:creeper",
EntityIds::ZOMBIE => "minecraft:zombie",
EntityIds::SKELETON_HORSE => "minecraft:skeleton_horse",
EntityIds::MULE => "minecraft:mule",
EntityIds::DONKEY => "minecraft:donkey",
EntityIds::DOLPHIN => "minecraft:dolphin",
EntityIds::TROPICALFISH => "minecraft:tropicalfish",
EntityIds::WOLF => "minecraft:wolf",
EntityIds::SQUID => "minecraft:squid",
EntityIds::DROWNED => "minecraft:drowned",
EntityIds::SHEEP => "minecraft:sheep",
EntityIds::MOOSHROOM => "minecraft:mooshroom",
EntityIds::PANDA => "minecraft:panda",
EntityIds::SALMON => "minecraft:salmon",
EntityIds::PIG => "minecraft:pig",
EntityIds::VILLAGER => "minecraft:villager",
EntityIds::COD => "minecraft:cod",
EntityIds::PUFFERFISH => "minecraft:pufferfish",
EntityIds::COW => "minecraft:cow",
EntityIds::CHICKEN => "minecraft:chicken",
EntityIds::BALLOON => "minecraft:balloon",
EntityIds::LLAMA => "minecraft:llama",
EntityIds::IRON_GOLEM => "minecraft:iron_golem",
EntityIds::RABBIT => "minecraft:rabbit",
EntityIds::SNOW_GOLEM => "minecraft:snow_golem",
EntityIds::BAT => "minecraft:bat",
EntityIds::OCELOT => "minecraft:ocelot",
EntityIds::HORSE => "minecraft:horse",
EntityIds::CAT => "minecraft:cat",
EntityIds::POLAR_BEAR => "minecraft:polar_bear",
EntityIds::ZOMBIE_HORSE => "minecraft:zombie_horse",
EntityIds::TURTLE => "minecraft:turtle",
EntityIds::PARROT => "minecraft:parrot",
EntityIds::GUARDIAN => "minecraft:guardian",
EntityIds::ELDER_GUARDIAN => "minecraft:elder_guardian",
EntityIds::VINDICATOR => "minecraft:vindicator",
EntityIds::WITHER => "minecraft:wither",
EntityIds::ENDER_DRAGON => "minecraft:ender_dragon",
EntityIds::SHULKER => "minecraft:shulker",
EntityIds::ENDERMITE => "minecraft:endermite",
EntityIds::MINECART => "minecraft:minecart",
EntityIds::HOPPER_MINECART => "minecraft:hopper_minecart",
EntityIds::TNT_MINECART => "minecraft:tnt_minecart",
EntityIds::CHEST_MINECART => "minecraft:chest_minecart",
EntityIds::COMMAND_BLOCK_MINECART => "minecraft:command_block_minecart",
EntityIds::ARMOR_STAND => "minecraft:armor_stand",
EntityIds::ITEM => "minecraft:item",
EntityIds::TNT => "minecraft:tnt",
EntityIds::FALLING_BLOCK => "minecraft:falling_block",
EntityIds::XP_BOTTLE => "minecraft:xp_bottle",
EntityIds::XP_ORB => "minecraft:xp_orb",
EntityIds::EYE_OF_ENDER_SIGNAL => "minecraft:eye_of_ender_signal",
EntityIds::ENDER_CRYSTAL => "minecraft:ender_crystal",
EntityIds::SHULKER_BULLET => "minecraft:shulker_bullet",
EntityIds::FISHING_HOOK => "minecraft:fishing_hook",
EntityIds::DRAGON_FIREBALL => "minecraft:dragon_fireball",
EntityIds::ARROW => "minecraft:arrow",
EntityIds::SNOWBALL => "minecraft:snowball",
EntityIds::EGG => "minecraft:egg",
EntityIds::PAINTING => "minecraft:painting",
EntityIds::THROWN_TRIDENT => "minecraft:thrown_trident",
EntityIds::FIREBALL => "minecraft:fireball",
EntityIds::SPLASH_POTION => "minecraft:splash_potion",
EntityIds::ENDER_PEARL => "minecraft:ender_pearl",
EntityIds::LEASH_KNOT => "minecraft:leash_knot",
EntityIds::WITHER_SKULL => "minecraft:wither_skull",
EntityIds::WITHER_SKULL_DANGEROUS => "minecraft:wither_skull_dangerous",
EntityIds::BOAT => "minecraft:boat",
EntityIds::LIGHTNING_BOLT => "minecraft:lightning_bolt",
EntityIds::SMALL_FIREBALL => "minecraft:small_fireball",
EntityIds::LLAMA_SPIT => "minecraft:llama_spit",
EntityIds::AREA_EFFECT_CLOUD => "minecraft:area_effect_cloud",
EntityIds::LINGERING_POTION => "minecraft:lingering_potion",
EntityIds::FIREWORKS_ROCKET => "minecraft:fireworks_rocket",
EntityIds::EVOCATION_FANG => "minecraft:evocation_fang",
EntityIds::EVOCATION_ILLAGER => "minecraft:evocation_illager",
EntityIds::VEX => "minecraft:vex",
EntityIds::AGENT => "minecraft:agent",
EntityIds::ICE_BOMB => "minecraft:ice_bomb",
EntityIds::PHANTOM => "minecraft:phantom",
EntityIds::TRIPOD_CAMERA => "minecraft:tripod_camera"
];
/** @var int|null */
public $entityUniqueId = null; //TODO
/** @var int */
@ -60,7 +169,10 @@ class AddEntityPacket extends DataPacket{
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->type = $this->getUnsignedVarInt();
$this->type = array_search($t = $this->getString(), self::LEGACY_ID_MAP_BC);
if($this->type === false){
throw new \UnexpectedValueException("Can't map ID $t to legacy ID");
}
$this->position = $this->getVector3();
$this->motion = $this->getVector3();
$this->pitch = $this->getLFloat();
@ -95,7 +207,7 @@ class AddEntityPacket extends DataPacket{
protected function encodePayload(){
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putUnsignedVarInt($this->type);
$this->putString(self::LEGACY_ID_MAP_BC[$this->type]);
$this->putVector3($this->position);
$this->putVector3Nullable($this->motion);
$this->putLFloat($this->pitch);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,47 @@
<?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\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class BiomeDefinitionListPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::BIOME_DEFINITION_LIST_PACKET;
/** @var string */
public $namedtag;
protected function decodePayload(){
$this->namedtag = $this->getRemaining();
}
protected function encodePayload(){
$this->put($this->namedtag);
}
public function handle(NetworkSession $session) : bool{
return $session->handleBiomeDefinitionList($this);
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\CachedEncapsulatedPacket;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\utils\Utils;
@ -35,6 +36,8 @@ abstract class DataPacket extends NetworkBinaryStream{
/** @var bool */
public $isEncoded = false;
/** @var CachedEncapsulatedPacket */
public $__encapsulatedPacket = null;
/** @var int */
public $senderSubId = 0;
@ -136,10 +139,10 @@ abstract class DataPacket extends NetworkBinaryStream{
}
public function __get($name){
throw new \Error("Cannot read non-existing field \"$name\"");
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
}
public function __set($name, $value){
throw new \Error("Cannot write non-existing field \"$name\"");
throw new \Error("Undefined property: " . get_class($this) . "::\$" . $name);
}
}

View File

@ -250,8 +250,8 @@ class LevelSoundEventPacket extends DataPacket{
public $position;
/** @var int */
public $extraData = -1;
/** @var int */
public $pitch = 1;
/** @var string */
public $entityType = ":"; //???
/** @var bool */
public $isBabyMob = false; //...
/** @var bool */
@ -261,7 +261,7 @@ class LevelSoundEventPacket extends DataPacket{
$this->sound = $this->getByte();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->pitch = $this->getVarInt();
$this->entityType = $this->getString();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
@ -270,7 +270,7 @@ class LevelSoundEventPacket extends DataPacket{
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putVarInt($this->pitch);
$this->putString($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}

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\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
/**
* Useless leftover from a 1.8 refactor, does nothing
*/
class LevelSoundEventPacketV1 extends DataPacket{
public const NETWORK_ID = ProtocolInfo::LEVEL_SOUND_EVENT_PACKET_V1;
/** @var int */
public $sound;
/** @var Vector3 */
public $position;
/** @var int */
public $extraData = 0;
/** @var int */
public $entityType = 1;
/** @var bool */
public $isBabyMob = false; //...
/** @var bool */
public $disableRelativeVolume = false;
protected function decodePayload(){
$this->sound = $this->getByte();
$this->position = $this->getVector3();
$this->extraData = $this->getVarInt();
$this->entityType = $this->getVarInt();
$this->isBabyMob = $this->getBool();
$this->disableRelativeVolume = $this->getBool();
}
protected function encodePayload(){
$this->putByte($this->sound);
$this->putVector3($this->position);
$this->putVarInt($this->extraData);
$this->putVarInt($this->entityType);
$this->putBool($this->isBabyMob);
$this->putBool($this->disableRelativeVolume);
}
public function handle(NetworkSession $session) : bool{
return $session->handleLevelSoundEventPacketV1($this);
}
}

View File

@ -0,0 +1,55 @@
<?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\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class NetworkChunkPublisherUpdatePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
/** @var int */
public $radius;
protected function decodePayload(){
$this->getSignedBlockPosition($this->x, $this->y, $this->z);
$this->radius = $this->getUnsignedVarInt();
}
protected function encodePayload(){
$this->putSignedBlockPosition($this->x, $this->y, $this->z);
$this->putUnsignedVarInt($this->radius);
}
public function handle(NetworkSession $session) : bool{
return $session->handleNetworkChunkPublisherUpdate($this);
}
}

View File

@ -54,7 +54,7 @@ class PacketPool{
static::registerPacket(new UpdateBlockPacket());
static::registerPacket(new AddPaintingPacket());
static::registerPacket(new ExplodePacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new LevelSoundEventPacketV1());
static::registerPacket(new LevelEventPacket());
static::registerPacket(new BlockEventPacket());
static::registerPacket(new EntityEventPacket());
@ -147,6 +147,11 @@ class PacketPool{
static::registerPacket(new UpdateSoftEnumPacket());
static::registerPacket(new NetworkStackLatencyPacket());
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new SpawnParticleEffectPacket());
static::registerPacket(new AvailableEntityIdentifiersPacket());
static::registerPacket(new LevelSoundEventPacket());
static::registerPacket(new NetworkChunkPublisherUpdatePacket());
static::registerPacket(new BiomeDefinitionListPacket());
static::registerPacket(new BatchPacket());
}
@ -178,4 +183,5 @@ class PacketPool{
return $pk;
}
}

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 291;
public const CURRENT_PROTOCOL = 313;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.7.0';
public const MINECRAFT_VERSION = 'v1.8.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.7.0';
public const MINECRAFT_VERSION_NETWORK = '1.8.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -72,7 +72,7 @@ interface ProtocolInfo{
public const UPDATE_BLOCK_PACKET = 0x15;
public const ADD_PAINTING_PACKET = 0x16;
public const EXPLODE_PACKET = 0x17;
public const LEVEL_SOUND_EVENT_PACKET = 0x18;
public const LEVEL_SOUND_EVENT_PACKET_V1 = 0x18;
public const LEVEL_EVENT_PACKET = 0x19;
public const BLOCK_EVENT_PACKET = 0x1a;
public const ENTITY_EVENT_PACKET = 0x1b;
@ -164,6 +164,12 @@ interface ProtocolInfo{
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
public const SPAWN_PARTICLE_EFFECT_PACKET = 0x76;
public const AVAILABLE_ENTITY_IDENTIFIERS_PACKET = 0x77;
public const LEVEL_SOUND_EVENT_PACKET = 0x78;
public const NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET = 0x79;
public const BIOME_DEFINITION_LIST_PACKET = 0x7a;
}

View File

@ -41,6 +41,9 @@ class ResourcePackStackPacket extends DataPacket{
/** @var ResourcePack[] */
public $resourcePackStack = [];
/** @var bool */
public $isExperimental = false;
protected function decodePayload(){
$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getUnsignedVarInt();
@ -56,6 +59,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->getString();
$this->getString();
}
$this->isExperimental = $this->getBool();
}
protected function encodePayload(){
@ -74,6 +79,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->putString($entry->getPackVersion());
$this->putString(""); //TODO: subpack name
}
$this->putBool($this->isExperimental);
}
public function handle(NetworkSession $session) : bool{

View File

@ -0,0 +1,57 @@
<?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\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
class SpawnParticleEffectPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SPAWN_PARTICLE_EFFECT_PACKET;
/** @var int */
public $dimensionId = DimensionIds::OVERWORLD; //wtf mojang
/** @var Vector3 */
public $position;
/** @var string */
public $particleName;
protected function decodePayload(){
$this->dimensionId = $this->getByte();
$this->position = $this->getVector3();
$this->particleName = $this->getString();
}
protected function encodePayload(){
$this->putByte($this->dimensionId);
$this->putVector3($this->position);
$this->putString($this->particleName);
}
public function handle(NetworkSession $session) : bool{
return $session->handleSpawnParticleEffect($this);
}
}

View File

@ -118,6 +118,10 @@ class StartGamePacket extends DataPacket{
public $isFromLockedWorldTemplate = false;
/** @var bool */
public $useMsaGamertagsOnly = false;
/** @var bool */
public $isFromWorldTemplate = false;
/** @var bool */
public $isWorldTemplateOptionLocked = false;
/** @var string */
public $levelId = ""; //base64 string, usually the same as world folder name in vanilla
@ -176,6 +180,8 @@ class StartGamePacket extends DataPacket{
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
$this->useMsaGamertagsOnly = $this->getBool();
$this->isFromWorldTemplate = $this->getBool();
$this->isWorldTemplateOptionLocked = $this->getBool();
$this->levelId = $this->getString();
$this->worldName = $this->getString();
@ -236,6 +242,8 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);
$this->putBool($this->useMsaGamertagsOnly);
$this->putBool($this->isFromWorldTemplate);
$this->putBool($this->isWorldTemplateOptionLocked);
$this->putString($this->levelId);
$this->putString($this->worldName);

View File

@ -43,6 +43,8 @@ class UpdateTradePacket extends DataPacket{
public $varint1;
/** @var int */
public $varint2;
/** @var int */
public $varint3;
/** @var bool */
public $isWilling;
/** @var int */
@ -59,6 +61,7 @@ class UpdateTradePacket extends DataPacket{
$this->windowType = $this->getByte();
$this->varint1 = $this->getVarInt();
$this->varint2 = $this->getVarInt();
$this->varint3 = $this->getVarInt();
$this->isWilling = $this->getBool();
$this->traderEid = $this->getEntityUniqueId();
$this->playerEid = $this->getEntityUniqueId();
@ -71,6 +74,7 @@ class UpdateTradePacket extends DataPacket{
$this->putByte($this->windowType);
$this->putVarInt($this->varint1);
$this->putVarInt($this->varint2);
$this->putVarInt($this->varint3);
$this->putBool($this->isWilling);
$this->putEntityUniqueId($this->traderEid);
$this->putEntityUniqueId($this->playerEid);

View File

@ -36,6 +36,7 @@ class NetworkInventoryAction{
public const SOURCE_WORLD = 2; //drop/pickup item entity
public const SOURCE_CREATIVE = 3;
public const SOURCE_CRAFTING_GRID = 100;
public const SOURCE_TODO = 99999;
/**
@ -80,7 +81,7 @@ class NetworkInventoryAction{
/** @var int */
public $sourceType;
/** @var int */
public $windowId = ContainerIds::NONE;
public $windowId;
/** @var int */
public $sourceFlags = 0;
/** @var int */
@ -107,6 +108,7 @@ class NetworkInventoryAction{
break;
case self::SOURCE_CREATIVE:
break;
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
$this->windowId = $packet->getVarInt();
switch($this->windowId){
@ -118,6 +120,8 @@ class NetworkInventoryAction{
break;
}
break;
default:
throw new \UnexpectedValueException("Unknown inventory action source type $this->sourceType");
}
$this->inventorySlot = $packet->getUnsignedVarInt();
@ -142,9 +146,12 @@ class NetworkInventoryAction{
break;
case self::SOURCE_CREATIVE:
break;
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
$packet->putVarInt($this->windowId);
break;
default:
throw new \UnexpectedValueException("Unknown inventory action source type $this->sourceType");
}
$packet->putUnsignedVarInt($this->inventorySlot);
@ -186,27 +193,17 @@ class NetworkInventoryAction{
}
return new CreativeInventoryAction($this->oldItem, $this->newItem, $type);
case self::SOURCE_CRAFTING_GRID:
case self::SOURCE_TODO:
//These types need special handling.
switch($this->windowId){
case self::SOURCE_TYPE_CRAFTING_ADD_INGREDIENT:
case self::SOURCE_TYPE_CRAFTING_REMOVE_INGREDIENT:
$window = $player->getCraftingGrid();
return new SlotChangeAction($window, $this->inventorySlot, $this->oldItem, $this->newItem);
case self::SOURCE_TYPE_CONTAINER_DROP_CONTENTS: //TODO: this type applies to all fake windows, not just crafting
return new SlotChangeAction($player->getCraftingGrid(), $this->inventorySlot, $this->oldItem, $this->newItem);
case self::SOURCE_TYPE_CRAFTING_RESULT:
case self::SOURCE_TYPE_CRAFTING_USE_INGREDIENT:
return null;
case self::SOURCE_TYPE_CONTAINER_DROP_CONTENTS:
//TODO: this type applies to all fake windows, not just crafting
$window = $player->getCraftingGrid();
//DROP_CONTENTS doesn't bother telling us what slot the item is in, so we find it ourselves
$inventorySlot = $window->first($this->oldItem, true);
if($inventorySlot === -1){
throw new \InvalidStateException("Fake container " . get_class($window) . " for " . $player->getName() . " does not contain $this->oldItem");
}
return new SlotChangeAction($window, $inventorySlot, $this->oldItem, $this->newItem);
}
//TODO: more stuff

View File

@ -96,7 +96,8 @@ class RCON{
$response = new RemoteConsoleCommandSender();
$command = $this->instance->cmd;
$this->server->getPluginManager()->callEvent($ev = new RemoteServerCommandEvent($response, $command));
$ev = new RemoteServerCommandEvent($response, $command);
$ev->call();
if(!$ev->isCancelled()){
$this->server->dispatchCommand($ev->getSender(), $ev->getCommand());

View File

@ -68,7 +68,7 @@ class RCONInstance extends Thread{
$this->ipcSocket = $ipcSocket;
$this->notifier = $notifier;
$this->start(PTHREADS_INHERIT_INI); //HACK: need INI for timezone (logger)
$this->start(PTHREADS_INHERIT_NONE);
}
private function writePacket($client, int $requestID, int $packetType, string $payload){

View File

@ -42,6 +42,8 @@ class Permission{
* @param bool|string $value
*
* @return string
*
* @throws \InvalidArgumentException
*/
public static function getByName($value) : string{
if(is_bool($value)){
@ -70,10 +72,11 @@ class Permission{
case "true":
return self::DEFAULT_TRUE;
default:
case "false":
return self::DEFAULT_FALSE;
}
throw new \InvalidArgumentException("Unknown permission default name \"$value\"");
}
/**

View File

@ -43,7 +43,6 @@ use pocketmine\utils\Utils;
* Manages all the plugins
*/
class PluginManager{
private const MAX_EVENT_CALL_DEPTH = 50;
/** @var Server */
private $server;
@ -66,9 +65,6 @@ class PluginManager{
*/
protected $fileAssociations = [];
/** @var int */
private $eventCallDepth = 0;
/** @var string|null */
private $pluginDataDirectory;
@ -569,7 +565,7 @@ class PluginManager{
$this->enabledPlugins[$plugin->getDescription()->getName()] = $plugin;
$this->server->getPluginManager()->callEvent(new PluginEnableEvent($plugin));
(new PluginEnableEvent($plugin))->call();
}catch(\Throwable $e){
$this->server->getLogger()->logException($e);
$this->disablePlugin($plugin);
@ -646,7 +642,7 @@ class PluginManager{
public function disablePlugin(Plugin $plugin){
if($plugin->isEnabled()){
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.disable", [$plugin->getDescription()->getFullName()]));
$this->callEvent(new PluginDisableEvent($plugin));
(new PluginDisableEvent($plugin))->call();
unset($this->enabledPlugins[$plugin->getDescription()->getName()]);
@ -680,44 +676,13 @@ class PluginManager{
/**
* Calls an event
*
* @deprecated
* @see Event::call()
*
* @param Event $event
*/
public function callEvent(Event $event){
if($this->eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
//this exception will be caught by the parent event call if all else fails
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
}
$handlerList = HandlerList::getHandlerListFor(get_class($event));
assert($handlerList !== null, "Called event should have a valid HandlerList");
++$this->eventCallDepth;
foreach(EventPriority::ALL as $priority){
$currentList = $handlerList;
while($currentList !== null){
foreach($currentList->getListenersByPriority($priority) as $registration){
if(!$registration->getPlugin()->isEnabled()){
continue;
}
try{
$registration->callEvent($event);
}catch(\Throwable $e){
$this->server->getLogger()->critical(
$this->server->getLanguage()->translateString("pocketmine.plugin.eventError", [
$event->getEventName(),
$registration->getPlugin()->getDescription()->getFullName(),
$e->getMessage(),
get_class($registration->getListener())
]));
$this->server->getLogger()->logException($e);
}
}
$currentList = $currentList->getParent();
}
}
--$this->eventCallDepth;
$event->call();
}
/**
@ -745,11 +710,14 @@ class PluginManager{
if(count($parameters) !== 1){
continue;
}
$handlerClosure = $method->getClosure($listener);
try{
$eventClass = $parameters[0]->getClass();
}catch(\ReflectionException $e){ //class doesn't exist
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
$this->server->getLogger()->debug("Not registering @softDepend listener " . get_class($listener) . "::" . $method->getName() . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
$this->server->getLogger()->debug("Not registering @softDepend listener " . Utils::getNiceClosureName($handlerClosure) . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
continue;
}
@ -762,7 +730,7 @@ class PluginManager{
try{
$priority = isset($tags["priority"]) ? EventPriority::fromString($tags["priority"]) : EventPriority::NORMAL;
}catch(\InvalidArgumentException $e){
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid/unknown priority \"" . $tags["priority"] . "\"");
}
$ignoreCancelled = false;
@ -776,7 +744,7 @@ class PluginManager{
$ignoreCancelled = false;
break;
default:
throw new PluginException("Event handler " . get_class($listener) . "->" . $method->getName() . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @ignoreCancelled value \"" . $tags["ignoreCancelled"] . "\"");
}
}

View File

@ -4,9 +4,6 @@
# New settings/defaults won't appear automatically in this file when upgrading.
settings:
#Three-letter language code for server-side localization
#Check your language code on https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
language: "eng"
#Whether to send all strings translated to server locale or let the device handle them
force-language: false
shutdown-message: "Server closed"

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\Server;
use pocketmine\utils\Utils;
/**
* Manages general-purpose worker threads used for processing asynchronous tasks, and the tasks submitted to those
@ -96,6 +97,7 @@ class AsyncPool{
* @param \Closure $hook
*/
public function addWorkerStartHook(\Closure $hook) : void{
Utils::validateCallableSignature(function(int $worker) : void{}, $hook);
$this->workerStartHooks[spl_object_hash($hook)] = $hook;
foreach($this->workers as $i => $worker){
$hook($i);
@ -292,26 +294,19 @@ class AsyncPool{
$task->checkProgressUpdates($this->server);
if($task->isGarbage() and !$task->isRunning() and !$task->isCrashed()){
if(!$task->hasCancelledRun()){
try{
/*
* It's possible for a task to submit a progress update and then finish before the progress
* update is detected by the parent thread, so here we consume any missed updates.
*
* When this happens, it's possible for a progress update to arrive between the previous
* checkProgressUpdates() call and the next isGarbage() call, causing progress updates to be
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
* been consumed before completing.
*/
$task->checkProgressUpdates($this->server);
$task->onCompletion($this->server);
if($task->removeDanglingStoredObjects()){
$this->logger->notice("AsyncTask " . get_class($task) . " stored local complex data but did not remove them after completion");
}
}catch(\Throwable $e){
$this->logger->critical("Could not execute completion of asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": " . $e->getMessage());
$this->logger->logException($e);
$task->removeDanglingStoredObjects(); //silent
/*
* It's possible for a task to submit a progress update and then finish before the progress
* update is detected by the parent thread, so here we consume any missed updates.
*
* When this happens, it's possible for a progress update to arrive between the previous
* checkProgressUpdates() call and the next isGarbage() call, causing progress updates to be
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
* been consumed before completing.
*/
$task->checkProgressUpdates($this->server);
$task->onCompletion($this->server);
if($task->removeDanglingStoredObjects()){
$this->logger->notice("AsyncTask " . get_class($task) . " stored local complex data but did not remove them after completion");
}
}

View File

@ -107,11 +107,9 @@ abstract class AsyncTask extends Collectable{
/**
* @param mixed $result
* @param bool $serialize
*/
public function setResult($result, bool $serialize = true){
$this->result = $serialize ? serialize($result) : $result;
$this->serialized = $serialize;
public function setResult($result){
$this->result = ($this->serialized = !is_scalar($result)) ? serialize($result) : $result;
}
public function setTaskId(int $taskId){

View File

@ -0,0 +1,59 @@
<?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\scheduler;
use pocketmine\utils\Utils;
/**
* Task implementation which allows closures to be called by a scheduler.
*
* Example usage:
*
* ```
* TaskScheduler->scheduleTask(new ClosureTask(function(int $currentTick) : void{
* echo "HI on $currentTick\n";
* });
* ```
*/
class ClosureTask extends Task{
/** @var \Closure */
private $closure;
/**
* @param \Closure $closure Must accept only ONE parameter, $currentTick
*/
public function __construct(\Closure $closure){
Utils::validateCallableSignature(function(int $currentTick) : void{}, $closure);
$this->closure = $closure;
}
public function getName() : string{
return Utils::getNiceClosureName($this->closure);
}
public function onRun(int $currentTick){
($this->closure)($currentTick);
}
}

View File

@ -30,8 +30,6 @@ namespace pocketmine\scheduler;
use pocketmine\utils\ReversePriorityQueue;
class TaskScheduler{
/** @var \Logger */
private $logger;
/** @var string|null */
private $owner;
@ -54,9 +52,11 @@ class TaskScheduler{
/** @var int */
protected $currentTick = 0;
/**
* @param \Logger $logger @deprecated
* @param null|string $owner
*/
public function __construct(\Logger $logger, ?string $owner = null){
$this->logger = $logger;
$this->owner = $owner;
$this->queue = new ReversePriorityQueue();
}
@ -108,11 +108,9 @@ class TaskScheduler{
if(isset($this->tasks[$taskId])){
try{
$this->tasks[$taskId]->cancel();
}catch(\Throwable $e){
$this->logger->critical("Task " . $this->tasks[$taskId]->getTaskName() . " threw an exception when trying to cancel: " . $e->getMessage());
$this->logger->logException($e);
}finally{
unset($this->tasks[$taskId]);
}
unset($this->tasks[$taskId]);
}
}
@ -198,26 +196,14 @@ class TaskScheduler{
unset($this->tasks[$task->getTaskId()]);
continue;
}
$crashed = false;
try{
$task->run($this->currentTick);
}catch(\Throwable $e){
$crashed = true;
$this->logger->critical("Could not execute task " . $task->getTaskName() . ": " . $e->getMessage());
$this->logger->logException($e);
}
$task->run($this->currentTick);
if($task->isRepeating()){
if($crashed){
$this->logger->debug("Dropping repeating task " . $task->getTaskName() . " due to exceptions thrown while running");
}else{
$task->setNextRun($this->currentTick + $task->getPeriod());
$this->queue->insert($task, $this->currentTick + $task->getPeriod());
continue;
}
$task->setNextRun($this->currentTick + $task->getPeriod());
$this->queue->insert($task, $this->currentTick + $task->getPeriod());
}else{
$task->remove();
unset($this->tasks[$task->getTaskId()]);
}
$task->remove();
unset($this->tasks[$task->getTaskId()]);
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\tile;
use pocketmine\inventory\ChestInventory;
use pocketmine\inventory\DoubleChestInventory;
use pocketmine\inventory\InventoryHolder;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
@ -108,7 +109,7 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
}
protected function checkPairing(){
if($this->isPaired() and !$this->getLevel()->isChunkLoaded($this->pairX >> 4, $this->pairZ >> 4)){
if($this->isPaired() and !$this->getLevel()->isInLoadedTerrain(new Vector3($this->pairX, $this->y, $this->pairZ))){
//paired to a tile in an unloaded chunk
$this->doubleInventory = null;

View File

@ -136,8 +136,8 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
}
protected function checkFuel(Item $fuel){
$this->server->getPluginManager()->callEvent($ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime()));
$ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime());
$ev->call();
if($ev->isCancelled()){
return;
}
@ -189,7 +189,8 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
if($this->cookTime >= 200){ //10 seconds
$product = ItemFactory::get($smelt->getResult()->getId(), $smelt->getResult()->getDamage(), $product->getCount() + 1);
$this->server->getPluginManager()->callEvent($ev = new FurnaceSmeltEvent($this, $raw, $product));
$ev = new FurnaceSmeltEvent($this, $raw, $product);
$ev->call();
if(!$ev->isCancelled()){
$this->inventory->setResult($ev->getResult());

View File

@ -143,7 +143,7 @@ class Sign extends Spawnable{
$removeFormat = $player->getRemoveFormat();
$ev = new SignChangeEvent($this->getBlock(), $player, array_map(function(string $line) use ($removeFormat){ return TextFormat::clean($line, $removeFormat); }, $lines));
$this->level->getServer()->getPluginManager()->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$this->setText(...$ev->getLines());

View File

@ -67,8 +67,7 @@ abstract class Spawnable extends Tile{
return;
}
$pk = $this->createSpawnPacket();
$this->level->addChunkPacket($this->getFloorX() >> 4, $this->getFloorZ() >> 4, $pk);
$this->level->broadcastPacketToViewers($this, $this->createSpawnPacket());
}
/**

View File

@ -62,7 +62,7 @@ class AutoUpdater{
$this->updateInfo = $updateInfo;
$this->checkUpdate();
if($this->hasUpdate()){
$this->server->getPluginManager()->callEvent(new UpdateNotifyEvent($this));
(new UpdateNotifyEvent($this))->call();
if($this->server->getProperty("auto-updater.on-update.warn-console", true)){
$this->showConsoleUpdate();
}

View File

@ -59,7 +59,7 @@ class UpdateCheckTask extends AsyncTask{
isset($response["download_url"])
){
$response["details_url"] = $response["details_url"] ?? null;
$this->setResult($response, true);
$this->setResult($response);
}elseif(isset($response["error"])){
$this->error = $response["error"];
}else{

View File

@ -23,9 +23,6 @@ declare(strict_types=1);
namespace pocketmine\utils;
use pocketmine\Server;
/**
* Config Class for simple config manipulation of multiple formats.
*/
@ -189,37 +186,29 @@ class Config{
*/
public function save() : bool{
if($this->correct){
try{
$content = null;
switch($this->type){
case Config::PROPERTIES:
$content = $this->writeProperties();
break;
case Config::JSON:
$content = json_encode($this->config, $this->jsonOptions);
break;
case Config::YAML:
$content = yaml_emit($this->config, YAML_UTF8_ENCODING);
break;
case Config::SERIALIZED:
$content = serialize($this->config);
break;
case Config::ENUM:
$content = implode("\r\n", array_keys($this->config));
break;
default:
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
}
file_put_contents($this->file, $content);
}catch(\Throwable $e){
$logger = Server::getInstance()->getLogger();
$logger->critical("Could not save Config " . $this->file . ": " . $e->getMessage());
if(\pocketmine\DEBUG > 1){
$logger->logException($e);
}
$content = null;
switch($this->type){
case Config::PROPERTIES:
$content = $this->writeProperties();
break;
case Config::JSON:
$content = json_encode($this->config, $this->jsonOptions);
break;
case Config::YAML:
$content = yaml_emit($this->config, YAML_UTF8_ENCODING);
break;
case Config::SERIALIZED:
$content = serialize($this->config);
break;
case Config::ENUM:
$content = implode("\r\n", array_keys($this->config));
break;
default:
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
}
file_put_contents($this->file, $content);
$this->changed = false;
return true;

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