Compare commits

...

261 Commits
3.2.2 ... 3.4.0

Author SHA1 Message Date
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
f1c071ce7f Release 3.3.3 2018-11-30 19:41:36 +00:00
e2f46a4358 Remove unused import... 2018-11-30 19:40:35 +00:00
36c0c350a7 Merge branch 'release/3.3' into release/3.4 2018-11-30 18:37:28 +00:00
4c08a05fae Barf on trying to read/write nonexisting fields of packets
this should make it easier to debug problems when content of packets changes during protocol updates.
2018-11-30 18:36: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
b473ffdedc Remove async playerdata saving, closes #2515
this technically involves non-breaking API changes which should happen on a patch release, but I can't be bothered with the dust cleanup, so we'll just blow it away now. It doesn't hurt anyone anyway.
2018-11-29 19:47:15 +00:00
60dddcd12a Painting: clean up guard checks, remove unnecessary checks 2018-11-29 19:29:10 +00:00
c010ef45ed Merge branch 'release/3.3' into release/3.4 2018-11-29 18:46:00 +00:00
93c26a0b0c Living: Suspend effects ticking on death
This was the cause of a bug with regeneration which caused players taking fatal damage under regeneration not to die correctly. On the server side they would die and immediately regenerate some health, which would cause the next attribute sync to not report the health drop to zero, which made the client unaware that it was dead.

Perhaps attributes should be forcibly synced in some circumstances, but nonetheless regeneration shouldn't apply post-death.
2018-11-29 18:45:46 +00:00
08ec021f78 Merge branch 'release/3.3' into release/3.4 2018-11-26 14:02:32 +00:00
545ec9c881 Updated PreProcessor submodule 2018-11-26 14:02:23 +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
d5a1961e6b Force minimum uptime to be >= 120 seconds if a crash occurs (#2534)
This is an incremental improvement over 4a6841a5a4. This change works better because it also reduces disk spam of crashdumps.

This will now sleep if the server uptime was less than 120 seconds before crashing. If unattended, this will clamp down on automated crashdump spam. If attended, the user can simply press CTRL+C to abort the process and skip the delay.
2018-11-25 14:35:35 +00:00
449dda83fb Merge branch 'release/3.3' into release/3.4 2018-11-22 16:48:57 +00:00
6bc79149c3 SubChunk: Fixed $changed not getting set in setBlock() when only block data changed
it was comparing a string and an int. This now compares the integer values first.
2018-11-22 16:47:25 +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
e018311e73 Make start script errors a bit more noob-friendly 2018-11-13 18:23:54 +00:00
de50f02076 Merge branch 'release/3.3' into release/3.4 2018-11-12 22:07:22 +00:00
71d02e5870 Improve dev build error messages 2018-11-12 22:07:14 +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
d312aef1ac 3.3.3 is next 2018-11-11 11:58:51 +00:00
200de3fe84 Release 3.3.2 2018-11-11 11:58:25 +00:00
f560a6efea Merge tag '3.2.7' into release/3.3 2018-11-11 11:24:23 +00:00
7ecd7fd13f Release 3.2.7 2018-11-11 11:23:20 +00:00
5284ad0346 Merge branch 'release/3.3' into release/3.4 2018-11-11 11:15:46 +00:00
b893645a81 Merge branch 'release/3.2' into release/3.3 2018-11-11 11:15:39 +00:00
b19b3134ad PluginManager: reduce unnecessary indentation 2018-11-11 11:15:27 +00:00
7cf36f460b Merge branch 'release/3.3' into release/3.4 2018-11-10 22:37:08 +00:00
243f86b0a0 Merge branch 'release/3.2' into release/3.3 2018-11-10 22:37:02 +00:00
9156cbc269 PluginManager: Make registerEvents() check order more logical
Prioritize validating that the function is actually a handler, before trying to parse its doc comment.
2018-11-10 22:36:46 +00:00
a5f776af2f Merge branch 'release/3.3' into release/3.4 2018-11-07 22:11:13 +00:00
43fe6a1934 Merge branch 'release/3.2' into release/3.3 2018-11-07 20:02:28 +00:00
342a74ffcb Level: Collect garbage from chunk internals in doChunkGarbageCollection() 2018-11-07 20:01:07 +00:00
3d2701e775 Merge branch 'release/3.3' into release/3.4 2018-11-04 23:32:56 +00:00
2183bf875c Merge remote-tracking branch 'origin/release/3.2' into release/3.3 2018-11-04 23:32:33 +00:00
8cc2a4ce5d Remove start script support for source-code installations (#2495)
This was suggested recently by @TheDeibo. We don't want users running source-code installations unless they are developers, and developers should know how to boot a source-code installation anyway.
2018-11-04 23:31:57 +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
f61e14e341 Merge branch 'release/3.2' into release/3.3 2018-11-04 22:38:45 +00:00
7b24fbc8db Utils: fix a mistake in getNiceClassName() doc 2018-11-04 22:38:38 +00:00
0543c17849 Merge branch 'release/3.3' into release/3.4 2018-11-04 22:15:46 +00:00
c4f3426bae Merge branch 'release/3.2' into release/3.3 2018-11-04 22:15:21 +00:00
046c39b02e Remove some Throwable abuse in AsyncTasks 2018-11-04 22:15:06 +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
5c3eed40b3 Merge branch 'release/3.2' into release/3.3 2018-11-04 11:57:28 +00:00
3e5237b6e0 ItemEntity: remove useless instanceof 2018-11-04 11:57:22 +00:00
af1227f154 Merge branch 'release/3.3' into release/3.4 2018-11-03 19:43:54 +00:00
d9a867016c Merge branch 'release/3.2' into release/3.3 2018-11-03 19:43:45 +00:00
a50a863ab7 Chunk: be more intelligent about fast-serializing chunks
This reduces the amount of useless data that pthreads has to copy around.
2018-11-03 19:43:35 +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
1b03168b88 Merge branch 'release/3.2' into release/3.3 2018-11-03 15:12:40 +00:00
6b9fee05d6 Fixed performance bug with chunk sending
this process of fast-serialization, fast-deserialize, network-serialize is an order of magnitude slower than just doing the network encode directly on the main thread, and also copies more useless data.

For the main thread, the figures were something like 3x more expensive, and then an extra 7x for deserialization on the worker thread. This is a ridiculously large overhead.
2018-11-03 15:12:30 +00:00
44d8a5528e Merge branch 'release/3.3' into release/3.4 2018-11-03 12:12:42 +00:00
45a18ffe1e Merge branch 'release/3.2' into release/3.3 2018-11-03 12:12:23 +00:00
f0182c9996 TaskHandler: remove incorrect internal warning
this is perfectly fine to use, and preferable to getting a cyclic ref to the scheduler. TaskScheduler->cancelTask() does pretty much the exact same thing, and the scheduler internals are designed to deal with this anyway.
2018-11-03 12:12:02 +00:00
265b61b3e6 Merge branch 'release/3.3' into release/3.4 2018-10-31 18:55:26 +00:00
2d88058710 Merge branch 'release/3.2' into release/3.3 2018-10-31 18:55:18 +00:00
ab48d85c35 Properly deal with anonymous tasks in timings 2018-10-31 18:51:30 +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
d9b7a28747 Merge branch 'release/3.2' into release/3.3 2018-10-30 15:42:52 +00:00
31ceafa111 Chest: keep inventory better in sync when paired
should fix #2502
2018-10-30 15:42:44 +00:00
a0eb6e23e5 Merge branch 'release/3.3' into release/3.4 2018-10-29 12:42:17 +00:00
694d7d4e20 Merge branch 'release/3.2' into release/3.3 2018-10-29 12:42:10 +00:00
2da2fdd6d4 Added a test for Item->equals() when both items have no NBT 2018-10-29 12:42:03 +00:00
0aa30295af Merge branch 'release/3.3' into release/3.4 2018-10-26 20:09:02 +01:00
c1c56f29bb Merge branch 'release/3.2' into release/3.3 2018-10-26 20:08:55 +01:00
9b820a0849 Guard against possible overflow bug in NetworkBinaryStream 2018-10-26 20:08:48 +01:00
c6a4bc4bf7 Merge branch 'release/3.3' into release/3.4 2018-10-25 19:36:44 +01:00
3128449033 3.3.2 is next 2018-10-25 19:36:20 +01:00
a60154e0b7 Release 3.3.1 2018-10-25 19:29:57 +01:00
4cbbf2e91c Merge branch 'release/3.2' into release/3.3 2018-10-25 19:29:34 +01:00
b0624aff9f 3.2.7 is next 2018-10-25 19:28:54 +01:00
c38e2c5ccb Release 3.2.6 2018-10-25 19:20:26 +01:00
02ef0bfbb4 Remove unnecessary quotes
these appear on the echoed message, which is undesirable.
2018-10-25 18:49:06 +01:00
a714612453 Merge branch 'release/3.3' into release/3.4 2018-10-25 18:42:09 +01:00
4835537886 Merge branch 'release/3.2' into release/3.3 2018-10-25 18:41:58 +01:00
4a6841a5a4 Added client side self rate-limiting for crashdump reporting
this should produce some reduction in spam at the source.

This could also be used to control the rate at which constantly-crashing servers restart to stop them spamming the disk as well, but the main concern here is eliminating crash archive involuntary DDoS by crashy servers.
2018-10-25 18:10:59 +01:00
f61e099828 Merge branch 'release/3.3' into release/3.4 2018-10-24 15:52:04 +01:00
925da62afa Merge branch 'release/3.2' into release/3.3 2018-10-24 15:49:09 +01:00
09985c5763 Fixed async light population producing garbage when generator isn't registered, closes #2488 2018-10-24 15:49:00 +01:00
447b9562bb Merge branch 'release/3.3' into release/3.4 2018-10-24 12:16:46 +01:00
d1ee9eb960 Merge branch 'release/3.2' into release/3.3 2018-10-24 12:00:55 +01:00
196cf8a68d Fixed missing MoveEntityDeltaPacket field 2018-10-24 12:00:25 +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
5e68858ebf Merge branch 'release/3.2' into release/3.3 2018-10-21 18:16:59 +01:00
45c9caa38c Fixup some formatting issues 2018-10-21 18:15:25 +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
d749f19c73 Merge branch 'release/3.2' into release/3.3 2018-10-19 18:53:12 +01:00
41fd03f329 LightUpdate: fixed double-updated nodes not getting light propagated appropriately
This can happen when a light source is removed and later encountering another light source to fill the gap. A higher light level may get set and then not propagated. This bug is difficult to explain, but fairly easy to reproduce.
2018-10-19 18:53:04 +01:00
646c8970b8 Merge branch 'release/3.3' into release/3.4 2018-10-19 15:56:01 +01:00
58067b2ad1 Merge branch 'release/3.2' into release/3.3 2018-10-19 15:55:53 +01:00
0c9946621c Level: Do not tick chunks which have unloaded adjacent chunks
Grass can cause issues here by requesting blocks randomly offset away from itself, which can cause silent chunk loading on chunk ticking. It also causes crashes if chunk autoloading is taken away, which is obviously undesired.

It was also noticed that player chunkloaders cause chunks to start getting ticked as soon as they load their first chunk, which is before the entity is visible to everyone else on the server. This is probably undesired behaviour.
2018-10-19 15:48:46 +01:00
f1cd6940f9 Merge branch 'release/3.3' into release/3.4 2018-10-16 22:56:46 +01:00
af5637e050 PlayerListEntry: remove dead fields 2018-10-16 22:56:21 +01:00
4221e274d6 Merge branch 'release/3.3' into release/3.4 2018-10-16 18:20:14 +01:00
a524b0e447 3.3.1 is next 2018-10-16 18:19:49 +01:00
88a5e92c20 Release 3.3.0 2018-10-16 17:47:35 +01:00
b876ae4ef8 Merge branch 'release/3.2' into release/3.3 2018-10-16 17:26:46 +01:00
1983964f9e 3.2.6 is next 2018-10-16 17:26:04 +01:00
c4c55a45c9 Release 3.2.5 2018-10-16 17:17:53 +01:00
c5cd813b76 bump PM version 2018-10-16 17:15:49 +01:00
bc2dff3f51 version numbers 2018-10-16 17:15:26 +01:00
839d5eab7b Protocol changes for 1.7
there's also some new cases in stats, but we don't care about those anyway.
2018-10-16 17:13:52 +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
78923177f9 VersionString: use appropriate regex for number matching 2018-10-16 16:46:27 +01:00
df6bb2ea0e Merge branch 'release/3.2' into release/3.3 2018-10-16 09:51:19 +01:00
b7062e7bff CrashDump: don't try to report code that doesn't exist
this can happen when eval() is used, and then we get a big blank mess with nothing on it. eval() is a special case that should be handled separately, but for now this is just fixing a bug.
2018-10-16 09:50:59 +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
97980d4516 Update composer dependency versions 2018-10-12 09:31:19 +01:00
b261129788 Merge branch 'release/3.2' into release/3.3 2018-10-11 19:42:18 +01:00
d9220395d1 Dummy decode for ResourcePacksInfoPacket and ResourcePackStackPacket
while we can't deal with this information, it's needed for the sake of unit testing so we don't shit on every bit of incoming data of these packet types.
2018-10-11 19:42:00 +01:00
4f2f373a24 Merge branch 'release/3.2' into release/3.3 2018-10-10 13:41:51 +01:00
2858db430e Fixed AsyncTask publishProgress() race condition on task exit
It's possible for a progress update to be lost due to the task finishing before the main thread found the progress update.
2018-10-10 13:41:15 +01:00
de6d62aba2 Merge branch 'release/3.2' into release/3.3 2018-10-09 22:51:40 +01:00
32836cbfb8 Don't handle remaining packets in a batch when an earlier one triggered a disconnect 2018-10-09 22:50:02 +01:00
8316e00927 Player: Throw exception on failure to encode form JSON 2018-10-09 22:39:48 +01:00
6f694b0801 Merge branch 'release/3.2' into release/3.3 2018-10-07 19:45:26 +01:00
fd459cda54 3.2.5 is next 2018-10-07 19:45:06 +01:00
a66dd4a7d9 Release 3.2.4 2018-10-07 19:32:04 +01:00
3617eba4a3 Merge branch 'release/3.1' into release/3.2 2018-10-07 19:31:16 +01:00
e79cc98883 Release 3.1.8 2018-10-07 19:20:20 +01:00
a3552875cb Merge branch 'release/3.2' into release/3.3 2018-10-07 17:48:26 +01:00
d259b2c9ee Merge branch 'release/3.1' into release/3.2 2018-10-07 17:48:19 +01:00
10fa74b417 Make clear that Plugin->setEnabled() is @internal
Use of this by plugins will produce a lot of undefined behaviour, such as event handlers not being unregistered, scheduled tasks not being removed, and registered permissions causing memory leaks.
2018-10-07 17:48:11 +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
17ceb27af4 Merge branch 'release/3.1' into release/3.2 2018-10-06 14:45:05 +01:00
adbd1c7bed RCON: remove redundant sleep
this dates back to the days where PM used to kill threads to stop them. Today we're more civilized and ask it to stop nicely, so this isn't necessary anymore.
2018-10-06 14:44:56 +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
cf20e626e2 Merge branch 'release/3.1' into release/3.2 2018-10-05 17:43:54 +01:00
d75c830a7e Add -f parameter to lint.sh to allow it to not be useless in cygwin
find can conflict with windows' built in find command, which causes it to bug out when running tests.
2018-10-05 17:43:45 +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
722924a779 Merge branch 'release/3.1' into release/3.2 2018-10-04 16:40:55 +01:00
60e1b29462 RegionLoader: Remove incorrect size cap
This assumes that the region is properly garbage-collected and packed, but if the file contains uncollected garbage this may not be the case, resulting in a region larger than a gigabyte.
2018-10-04 16:40:45 +01:00
0171095036 Merge branch 'release/3.2' into release/3.3 2018-09-29 15:39:34 +01:00
5b511f6d06 Merge branch 'release/3.1' into release/3.2 2018-09-29 15:39:27 +01:00
426dee04a6 Potion: remove unnecessary exception throw in getPotionEffectsById()
this is only used by Potion and SplashPotion, and simply causes errors when trying to use potions with unknown IDs.
2018-09-29 15:39:20 +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
bb1944ca40 Merge branch 'release/3.1' into release/3.2 2018-09-26 13:12:20 +01:00
d1a20ecb4a CommandReader: Require readline to be explicitly enabled on Windows
readline on Windows causes issues with console output corruption. Additionally, PM readline impl is extremely buggy and probably ought to be removed. However, have a hotfix for now.
2018-09-26 13:11:21 +01:00
16c636df83 Merge branch 'release/3.2' into release/3.3 2018-09-24 18:27:04 -04:00
f6a8ec83a1 Merge branch 'release/3.1' into release/3.2 2018-09-24 18:26:39 -04:00
28137efb53 Fixed server freezing when using chorus fruit from large Y coordinates 2018-09-24 18:26:20 -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
7b0836d399 Merge branch 'release/3.1' into release/3.2 2018-09-23 16:35:11 +01:00
cea146e335 Thin: use bounding box instead of collision boxes 2018-09-23 16:35:01 +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
8db1ccc1ae Merge branch 'release/3.1' into release/3.2 2018-09-20 19:02:09 +01:00
5d56030afa Item: make nbtDeserialize() return AIR when reading an unknown PC item
This is scummy, but it's better than crashing the whole server just because a chest contained an unknown item.
2018-09-20 19:00:44 +01:00
6be5e75263 Merge branch 'release/3.2' into release/3.3 2018-09-20 17:04:55 +01:00
d9c251b613 Merge branch 'release/3.1' into release/3.2 2018-09-20 17:04:45 +01:00
8085b81f5c fix phars 2018-09-20 17:04:34 +01:00
6b44f99dfb Merge branch 'release/3.2' into release/3.3 2018-09-20 16:50:04 +01:00
33d3fff3c5 Merge branch 'release/3.1' into release/3.2 2018-09-20 16:49:57 +01:00
7c092b93b4 Fixed bug when placing blocks by clicking on redstone ore 2018-09-20 16:49:50 +01:00
aa05650994 Fixed block picking for mob heads 2018-09-20 13:11:45 +01:00
758d9b9784 Farmland: fixed block picking 2018-09-20 12:03:01 +01:00
24a6bf7365 PocketMine.php: Allow overriding autoloader path using --bootstrap
I've gotten tired of re-running composer every time I switch branches...
2018-09-20 12:01:39 +01:00
9a5d51fd3d Fixed block-picking cake giving the block instead of item 2018-09-20 11:31:48 +01:00
fa9ea6a7d7 Merge branch 'release/3.2' into release/3.3 2018-09-20 10:04:19 +01:00
6a7f39978b Merge branch 'release/3.1' into release/3.2 2018-09-20 10:03:47 +01:00
c52e1ea9f9 Fixed block picking double slabs giving the double slab block 2018-09-20 10:02:55 +01:00
5e94d20d79 Merge branch 'release/3.2' into release/3.3 2018-09-19 16:17:00 +01:00
a0bb747d6d Merge branch 'release/3.1' into release/3.2 2018-09-19 16:16:18 +01:00
4bc0d850b1 Added Block->getRuntimeId(), clean up some mess 2018-09-19 16:16:10 +01:00
ad9df6764d Merge branch 'release/3.2' into release/3.3 2018-09-18 12:32:07 +01:00
97583c8b04 Merge branch 'release/3.1' into release/3.2 2018-09-18 12:32:01 +01:00
107192c753 Bed: fixed block-pick giving wrong colour items 2018-09-18 12:31:53 +01:00
6309a242dc Merge branch 'release/3.2' into release/3.3 2018-09-18 12:22:26 +01:00
870f9abc20 Merge branch 'release/3.1' into release/3.2 2018-09-18 12:22:20 +01:00
0e2bbc44db Fixed drops and item picking of Brewing Stand 2018-09-18 12:22:12 +01:00
e58d015f14 Merge branch 'release/3.2' into release/3.3 2018-09-16 17:47:08 +01:00
d9768abe47 Merge branch 'release/3.1' into release/3.2 2018-09-16 17:47:01 +01:00
e9b84ecc8b Fixed incorrect break check for torch 2018-09-16 17:46:50 +01:00
0d65f9c4b8 Merge branch 'release/3.2' into release/3.3 2018-09-14 17:09:51 +01:00
c83d12790e Merge branch 'release/3.1' into release/3.2 2018-09-14 17:09:41 +01:00
5863d4c066 Fixed PermissibleBase->clearPermissions() not unsubscribing from permissions that aren't explicitly assigned
This came to light after observing cfb6856634 in a fresh light. I noticed that this fix should not have been necessary because clearPermissions() should have dealt with it. Unfortunately, permissions can be set without being set in PermissibleBase->permissions, so this misses things.
2018-09-14 17:06:32 +01:00
22077c1fdd Merge branch 'release/3.2' into release/3.3 2018-09-14 16:18:19 +01:00
7d54d18732 Merge branch 'release/3.1' into release/3.2 2018-09-14 16:18:12 +01:00
bfbc845efa Remove impossible uses of PlayerInteractEvent CLICK_AIR constants 2018-09-14 16:17:55 +01:00
f33c19e77a Merge branch 'release/3.2' into release/3.3 2018-09-14 11:06:24 +01:00
2ff4228fb7 Merge branch 'release/3.1' into release/3.2 2018-09-14 11:06:11 +01:00
06c4f31db7 Server: Account for later levels being unloaded by earlier levels' ticking function in checkTickUpdates()
should fix #2434

This happens when a plugin causes a level to be unloaded during an event fired on level tick.
2018-09-14 11:05:51 +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
6c70e84fa2 Merge branch 'release/3.1' into release/3.2 2018-09-11 19:47:46 +01:00
7d0e631a75 RakLibInterface: fixed processing hook being registered too early
this would cause bugs if the interface was not added directly to the network.
2018-09-11 19:47:26 +01:00
5134c0cf5a Merge branch 'release/3.2' into release/3.3 2018-09-11 12:28:09 +01:00
65b751d080 3.2.4 is next 2018-09-11 12:27:45 +01:00
27effff403 Release 3.2.3 2018-09-11 12:19:56 +01:00
a940cc5b5e Merge branch 'release/3.1' into release/3.2 2018-09-11 12:19:32 +01:00
15e654131c 3.1.8 is next 2018-09-11 12:18:54 +01:00
6e6cda91ce Release 3.1.7 2018-09-11 11:45:17 +01:00
0aa63d269a Merge branch 'release/3.2' into release/3.3 2018-09-11 11:35:46 +01:00
53a76c0d14 Merge branch 'release/3.1' into release/3.2 2018-09-11 11:35:38 +01:00
69500fe183 LightUpdate: Remove garbage left over from dab73d8950 2018-09-11 11:35:31 +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
5af4dd20df Merge branch 'release/3.1' into release/3.2 2018-09-08 14:25:11 +01:00
c7d58db7eb Cleanup Entity age handling, fixed arrows despawning too quickly after long flight 2018-09-08 14:23:06 +01:00
a3b78236eb Server: don't catch Throwable for level ticking
this usually causes the console to get spammed with errors. Additionally, in the case where doTick() throws any exception, it's usually because we're in a state we didn't want to be in, so we really should not carry on trying to keep ticking when something breaks here. Instead, this should generate a crashdump.
2018-09-08 14:13:28 +01:00
b70905b287 Merge branch 'release/3.2' into release/3.3 2018-09-06 19:31:05 +01:00
d8e27e6081 Bow: fix wrong arithmetic for Flame fire ticks, closes #2420 2018-09-06 19:30:55 +01:00
7a48c0b23d Merge branch 'release/3.2' into release/3.3 2018-09-06 19:15:41 +01:00
14a2ffa51b Merge branch 'release/3.1' into release/3.2 2018-09-06 18:43:28 +01:00
c447d51e3f Bucket: use ItemFactory instead of self-clone
in the future Item->setDamage() will be removed.
2018-09-06 18:42:09 +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
9f4722f537 3.2.3 is next 2018-09-04 11:56:27 +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
181 changed files with 2169 additions and 1268 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": {

68
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.1",
"version": "0.2.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8"
"reference": "291bf5cc2a94500eada1edbda51d15bed25a1e1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/a4ee39f313c6870153fb7c7e513b211217f7daf8",
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8",
"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.1",
"source": "https://github.com/pmmp/NBT/tree/0.2.3",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2018-09-04T10:36:02+00:00"
"time": "2018-12-03T16:08:28+00:00"
},
{
"name": "pocketmine/raklib",
@ -191,16 +232,16 @@
},
{
"name": "pocketmine/spl",
"version": "0.3.1",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/SPL.git",
"reference": "ca3912099543ddc4b4b14f40e258d84ca547dfa5"
"reference": "7fd53857cd000491ba69e8db865792a024dd2c49"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/SPL/zipball/ca3912099543ddc4b4b14f40e258d84ca547dfa5",
"reference": "ca3912099543ddc4b4b14f40e258d84ca547dfa5",
"url": "https://api.github.com/repos/pmmp/SPL/zipball/7fd53857cd000491ba69e8db865792a024dd2c49",
"reference": "7fd53857cd000491ba69e8db865792a024dd2c49",
"shasum": ""
},
"type": "library",
@ -219,14 +260,15 @@
"support": {
"source": "https://github.com/pmmp/SPL/tree/master"
},
"time": "2018-06-09T17:30:36+00:00"
"time": "2018-08-12T15:17:39+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"ext-pthreads": 20
"ext-pthreads": 20,
"daverandom/callback-validator": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View File

@ -212,11 +212,11 @@ class CrashDump{
$this->addLine("Code:");
$this->data["code"] = [];
if($this->server->getProperty("auto-report.send-code", true) !== false){
if($this->server->getProperty("auto-report.send-code", true) !== false and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10; ++$l){
$this->addLine("[" . ($l + 1) . "] " . @$file[$l]);
$this->data["code"][$l + 1] = @$file[$l];
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
$this->data["code"][$l + 1] = $file[$l];
}
}

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){
@ -473,7 +473,7 @@ class MemoryManager{
self::continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
}
}elseif(is_string($from)){
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);
$data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
}elseif(is_resource($from)){
$data = "(resource) " . print_r($from, true);
}else{

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

@ -173,6 +173,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* Checks a supplied username and checks it is valid.
*
* @param string $name
*
* @return bool
@ -284,14 +285,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 */
@ -546,9 +543,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;
}
@ -627,7 +621,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* @param Plugin $plugin
* @param string $name
* @param bool $value
* @param bool $value
*
* @return PermissionAttachment
*/
@ -800,7 +794,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]);
@ -1031,11 +1025,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());
}
@ -1220,7 +1215,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;
}
@ -1247,7 +1243,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);
@ -1287,7 +1283,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);
@ -1326,6 +1323,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* TODO: remove this when Spectator Mode gets added properly to MCPE
*
* @param int $gamemode
*
* @return int
*/
public static function getClientFriendlyGamemode(int $gamemode) : int{
@ -1350,7 +1348,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();
@ -1544,14 +1543,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){
@ -1567,7 +1561,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;
@ -1597,7 +1591,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
@ -1614,10 +1608,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){
@ -1641,7 +1631,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();
}
@ -1649,10 +1639,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;
@ -1706,6 +1692,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);
@ -1715,32 +1706,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;
}
}
}
}
@ -1898,7 +1863,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());
@ -2077,7 +2043,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());
@ -2179,8 +2146,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;
@ -2191,7 +2157,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());
}
@ -2234,9 +2201,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;
}
@ -2266,6 +2231,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* Don't expect much from this handler. Most of it is roughly hacked and duct-taped together.
*
* @param InventoryTransactionPacket $packet
*
* @return bool
*/
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
@ -2447,8 +2413,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;
@ -2593,7 +2558,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);
@ -2696,7 +2661,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());
}
@ -2721,12 +2686,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$target = $this->level->getBlock($pos);
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, $target->getId() === 0 ? PlayerInteractEvent::LEFT_CLICK_AIR : PlayerInteractEvent::LEFT_CLICK_BLOCK);
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
if($this->level->checkSpawnProtection($this, $target)){
$ev->setCancelled();
}
$this->getServer()->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
$this->inventory->sendHeldItem($this);
break;
@ -2785,7 +2750,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
break; //TODO
case PlayerActionPacket::ACTION_CONTINUE_BREAK:
$block = $this->level->getBlock($pos);
$this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, BlockFactory::toStaticRuntimeId($block->getId(), $block->getDamage()) | ($packet->face << 24));
$this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, $block->getRuntimeId() | ($packet->face << 24));
//TODO: destroy-progress level event
break;
case PlayerActionPacket::ACTION_START_SWIMMING:
@ -2805,7 +2770,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{
@ -2815,7 +2780,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{
@ -2828,7 +2793,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;
}
@ -2845,6 +2811,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* Drops an item on the ground in front of the player. Returns if the item drop was successful.
*
* @param Item $item
*
* @return bool if the item was dropped or if the item was null
*/
public function dropItem(Item $item) : bool{
@ -2872,7 +2839,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){
@ -2891,11 +2858,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{
@ -2963,7 +2931,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;
@ -3036,7 +3004,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;
}
@ -3071,7 +3040,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;
@ -3102,7 +3072,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;
}
@ -3144,14 +3115,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* Transfers a player to another server.
*
* @param string $address The IP address or hostname of the destination server
* @param int $port The destination port, defaults to 19132
* @param int $port The destination port, defaults to 19132
* @param string $message Message to show in the console when closing the player
*
* @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();
@ -3168,14 +3139,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* Kicks a player from the server
*
* @param string $reason
* @param bool $isAdmin
* @param string $reason
* @param bool $isAdmin
* @param TextContainer|string $quitMessage
*
* @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;
@ -3271,7 +3243,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* Internal function used for sending titles.
*
* @param string $title
* @param int $type
* @param int $type
*/
protected function sendTitleText(string $title, int $type){
$pk = new SetTitlePacket();
@ -3365,6 +3337,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk = new ModalFormRequestPacket();
$pk->formId = $id;
$pk->formData = json_encode($form);
if($pk->formData === false){
throw new \InvalidArgumentException("Failed to encode form JSON: " . json_last_error_msg());
}
if($this->dataPacket($pk)){
$this->forms[$id] = $form;
}
@ -3420,7 +3395,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());
}
@ -3508,11 +3484,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* Handles player data saving
*
* @param bool $async
*
* @throws \InvalidStateException if the player is closed
*/
public function save(bool $async = false){
public function save(){
if($this->closed){
throw new \InvalidStateException("Tried to save closed player");
}
@ -3549,7 +3523,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->namedtag->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
if($this->username != "" and $this->namedtag instanceof CompoundTag){
$this->server->saveOfflinePlayerData($this->username, $this->namedtag, $async);
$this->server->saveOfflinePlayerData($this->username, $this->namedtag);
}
}
@ -3568,7 +3542,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){
@ -3603,7 +3578,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);
@ -3801,6 +3777,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){
@ -3808,10 +3787,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.2.2";
const BASE_VERSION = "3.4.0";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;
@ -145,12 +145,18 @@ namespace pocketmine {
define('pocketmine\PATH', dirname(__FILE__, 3) . DIRECTORY_SEPARATOR);
}
define('pocketmine\COMPOSER_AUTOLOADER_PATH', \pocketmine\PATH . 'vendor/autoload.php');
$opts = getopt("", ["bootstrap:"]);
if(isset($opts["bootstrap"])){
$bootstrap = realpath($opts["bootstrap"]) ?: $opts["bootstrap"];
}else{
$bootstrap = \pocketmine\PATH . 'vendor/autoload.php';
}
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
if(is_file(\pocketmine\COMPOSER_AUTOLOADER_PATH)){
if(\pocketmine\COMPOSER_AUTOLOADER_PATH !== false and is_file(\pocketmine\COMPOSER_AUTOLOADER_PATH)){
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
}else{
critical_error("Composer autoloader not found.");
critical_error("Composer autoloader not found at " . $bootstrap);
critical_error("Please install/update Composer dependencies or use provided builds.");
exit(1);
}

View File

@ -91,7 +91,6 @@ use pocketmine\plugin\PluginManager;
use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\FileWriteTask;
use pocketmine\scheduler\SendUsageTask;
use pocketmine\snooze\SleeperHandler;
use pocketmine\snooze\SleeperNotifier;
@ -198,9 +197,6 @@ class Server{
/** @var ResourcePackManager */
private $resourceManager;
/** @var ConsoleCommandSender */
private $consoleSender;
/** @var int */
private $maxPlayers;
@ -556,10 +552,11 @@ class Server{
}
/**
* @deprecated
* @return bool
*/
public function getAllowFlight() : bool{
return $this->getConfigBool("allow-flight", false);
return true;
}
/**
@ -734,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
*
@ -805,22 +813,17 @@ class Server{
/**
* @param string $name
* @param CompoundTag $nbtTag
* @param bool $async
*/
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag, bool $async = false){
public function saveOfflinePlayerData(string $name, CompoundTag $nbtTag){
$ev = new PlayerDataSaveEvent($nbtTag, $name);
$ev->setCancelled(!$this->shouldSavePlayerData());
$this->pluginManager->callEvent($ev);
$ev->call();
if(!$ev->isCancelled()){
$nbt = new BigEndianNBTStream();
try{
if($async){
$this->asyncPool->submitTask(new FileWriteTask($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData())));
}else{
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
}
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
}catch(\Throwable $e){
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
$this->logger->logException($e);
@ -991,6 +994,7 @@ class Server{
/**
* @internal
*
* @param Level $level
*/
public function removeLevel(Level $level) : void{
@ -1033,7 +1037,7 @@ class Server{
$this->levels[$level->getId()] = $level;
$this->getPluginManager()->callEvent(new LevelLoadEvent($level));
(new LevelLoadEvent($level))->call();
$level->setTickRate($this->baseTickRate);
@ -1082,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]));
@ -1437,31 +1441,6 @@ class Server{
}
$this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []);
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->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))){
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error1", [\pocketmine\NAME]));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
$this->forceShutdown();
return;
}
if(((int) ini_get('zend.assertions')) > 0 and ((bool) $this->getProperty("debug.assertions.warn-if-enabled", true)) !== false){
$this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini.");
}
ini_set('assert.exception', '1');
if($this->logger instanceof MainLogger){
$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",
@ -1470,7 +1449,6 @@ class Server{
"announce-player-achievements" => true,
"spawn-protection" => 16,
"max-players" => 20,
"allow-flight" => false,
"spawn-animals" => true,
"spawn-mobs" => true,
"gamemode" => 0,
@ -1487,9 +1465,36 @@ class Server{
"rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10),
"auto-save" => true,
"view-distance" => 8,
"xbox-auth" => true
"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->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))){
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error1", [\pocketmine\NAME]));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error2"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error3"));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error4", ["settings.enable-dev-builds"]));
$this->logger->emergency($this->baseLang->translateString("pocketmine.server.devBuild.error5", ["https://github.com/pmmp/PocketMine-MP/releases"]));
$this->forceShutdown();
return;
}
if(((int) ini_get('zend.assertions')) > 0 and ((bool) $this->getProperty("debug.assertions.warn-if-enabled", true)) !== false){
$this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini.");
}
ini_set('assert.exception', '1');
if($this->logger instanceof MainLogger){
$this->logger->setLogDebug(\pocketmine\DEBUG > 1);
}
$this->memoryManager = new MemoryManager($this);
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.start", [TextFormat::AQUA . $this->getVersion() . TextFormat::RESET]));
@ -1527,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);
@ -1606,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();
@ -1623,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());
@ -1922,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
*
@ -1944,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;
}
@ -2195,6 +2200,14 @@ class Server{
if($this->getProperty("auto-report.enabled", true) !== false){
$report = true;
$stamp = $this->getDataPath() . "crashdumps/.last_crash";
$crashInterval = 120; //2 minutes
if(file_exists($stamp) and !($report = (filemtime($stamp) + $crashInterval < time()))){
$this->logger->debug("Not sending crashdump due to last crash less than $crashInterval seconds ago");
}
@touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"];
if(is_string($plugin)){
$p = $this->pluginManager->getPlugin($plugin);
@ -2237,6 +2250,12 @@ class Server{
$this->forceShutdown();
$this->isRunning = false;
//Force minimum uptime to be >= 120 seconds, to reduce the impact of spammy crash loops
$spacing = ((int) \pocketmine\START_TIME) - time() + 120;
if($spacing > 0){
sleep($spacing);
}
@Utils::kill(getmypid());
exit(1);
}
@ -2309,7 +2328,7 @@ class Server{
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, "", 0, $skin, $xboxUserId);
$pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skin, $xboxUserId);
$this->broadcastPacket($players ?? $this->playerList, $pk);
}
@ -2332,7 +2351,7 @@ class Server{
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
foreach($this->playerList as $player){
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), "", 0, $player->getSkin(), $player->getXuid());
$pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkin(), $player->getXuid());
}
$p->dataPacket($pk);
@ -2348,41 +2367,37 @@ class Server{
}
//Do level ticks
foreach($this->getLevels() as $level){
foreach($this->levels as $k => $level){
if(!isset($this->levels[$k])){
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
continue;
}
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
continue;
}
try{
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$level->tickRateCounter = $level->getTickRate();
}
}catch(\Throwable $e){
if(!$level->isClosed()){
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickError", [$level->getName(), $e->getMessage()]));
}else{
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickUnloadError", [$level->getName()]));
}
$this->logger->logException($e);
}
}
}
@ -2392,7 +2407,7 @@ class Server{
Timings::$worldSaveTimer->startTiming();
foreach($this->players as $index => $player){
if($player->spawned){
$player->save(true);
$player->save();
}elseif(!$player->isConnected()){
$this->removePlayer($player);
}
@ -2529,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

@ -192,21 +192,25 @@ class Bed extends Transparent{
public function getDropsForCompatibleTool(Item $item) : array{
if($this->isHeadPart()){
$tile = $this->getLevel()->getTile($this);
if($tile instanceof TileBed){
return [
ItemFactory::get($this->getItemId(), $tile->getColor())
];
}else{
return [
ItemFactory::get($this->getItemId(), 14) //Red
];
}
return [$this->getItem()];
}
return [];
}
public function getPickedItem() : Item{
return $this->getItem();
}
private function getItem() : Item{
$tile = $this->getLevel()->getTile($this);
if($tile instanceof TileBed){
return ItemFactory::get($this->getItemId(), $tile->getColor());
}
return ItemFactory::get($this->getItemId(), 14); //Red
}
public function isAffectedBySilkTouch() : bool{
return false;
}

View File

@ -109,6 +109,14 @@ class Block extends Position implements BlockIds, Metadatable{
return $this->itemId ?? $this->getId();
}
/**
* @internal
* @return int
*/
public function getRuntimeId() : int{
return BlockFactory::toStaticRuntimeId($this->getId(), $this->getDamage());
}
/**
* @return int
*/

View File

@ -330,6 +330,10 @@ class BlockFactory{
}
}
public static function isInit() : bool{
return self::$fullList !== null;
}
/**
* Registers a block type into the index. Plugins may use this method to register new block types or override
* existing ones.
@ -411,6 +415,7 @@ class BlockFactory{
* Returns whether a specified block ID is already registered in the block factory.
*
* @param int $id
*
* @return bool
*/
public static function isRegistered(int $id) : bool{

View File

@ -23,12 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\TieredTool;
class BrewingStand extends Transparent{
protected $id = self::BREWING_STAND_BLOCK;
protected $itemId = Item::BREWING_STAND;
public function __construct(int $meta = 0){
$this->meta = $meta;
}

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

@ -35,6 +35,8 @@ class Cake extends Transparent implements FoodSource{
protected $id = self::CAKE_BLOCK;
protected $itemId = Item::CAKE;
public function __construct(int $meta = 0){
$this->meta = $meta;
}

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

@ -47,4 +47,8 @@ abstract class DoubleSlab extends Solid{
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getPickedItem() : Item{
return ItemFactory::get($this->getSlabId(), $this->getVariant());
}
}

View File

@ -111,4 +111,8 @@ class Farmland extends Transparent{
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getPickedItem() : Item{
return ItemFactory::get(Item::DIRT);
}
}

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,6 +24,8 @@ 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\math\AxisAlignedBB;
@ -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->broadcastLevelSoundEvent($this->add(0.5, 0.5, 0.5), LevelSoundEventPacket::SOUND_FIZZ, (int) ((2.6 + (lcg_value() - lcg_value()) * 0.8) * 1000));
}
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

@ -50,7 +50,8 @@ class RedstoneOre extends Solid{
}
public function onActivate(Item $item, Player $player = null) : bool{
return $this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
$this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
return false; //this shouldn't prevent block placement
}
public function onNearbyBlockChange() : void{

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

@ -71,18 +71,20 @@ class Skull extends Flowable{
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
private function getItem() : Item{
$tile = $this->level->getTile($this);
if($tile instanceof TileSkull){
return [
ItemFactory::get(Item::SKULL, $tile->getType())
];
}
return ItemFactory::get(Item::SKULL, $tile instanceof TileSkull ? $tile->getType() : 0);
}
return [];
public function getDropsForCompatibleTool(Item $item) : array{
return [$this->getItem()];
}
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getPickedItem() : Item{
return $this->getItem();
}
}

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

@ -100,11 +100,7 @@ abstract class Thin extends Transparent{
}
//FIXME: currently there's no proper way to tell if a block is a full-block, so we check the bounding box size
$bbs = $block->getCollisionBoxes();
if(count($bbs) === 1){
return $bbs[0]->getAverageEdgeLength() >= 1;
}
return false;
$bb = $block->getBoundingBox();
return $bb !== null and $bb->getAverageEdgeLength() >= 1;
}
}

View File

@ -55,7 +55,7 @@ class Torch extends Flowable{
5 => Vector3::SIDE_DOWN
];
if($this->getSide($faces[$side])->isTransparent() and !($side === Vector3::SIDE_DOWN and ($below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL))){
if($this->getSide($faces[$side])->isTransparent() and !($faces[$side] === Vector3::SIDE_DOWN and ($below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL))){
$this->getLevel()->useBreakOn($this);
}
}

View File

@ -27,7 +27,7 @@ namespace pocketmine\command;
interface CommandMap{
/**
* @param string $fallbackPrefix
* @param string $fallbackPrefix
* @param Command[] $commands
*/
public function registerAll(string $fallbackPrefix, array $commands);

View File

@ -25,6 +25,7 @@ namespace pocketmine\command;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\Thread;
use pocketmine\utils\Utils;
class CommandReader extends Thread{
@ -47,9 +48,9 @@ class CommandReader extends Thread{
$this->buffer = new \Threaded;
$this->notifier = $notifier;
$opts = getopt("", ["disable-readline"]);
$opts = getopt("", ["disable-readline", "enable-readline"]);
if(extension_loaded("readline") and !isset($opts["disable-readline"]) and !$this->isPipe(STDIN)){
if(extension_loaded("readline") and (Utils::getOS() === "win" ? isset($opts["enable-readline"]) : !isset($opts["disable-readline"])) and !$this->isPipe(STDIN)){
$this->type = self::TYPE_READLINE;
}
}
@ -94,6 +95,7 @@ class CommandReader extends Thread{
* Checks if the specified stream is a FIFO pipe.
*
* @param resource $stream
*
* @return bool
*/
private function isPipe($stream) : bool{

View File

@ -54,6 +54,7 @@ interface CommandSender extends Permissible{
/**
* Sets the line height used for command output pagination for this command sender. `null` will reset it to default.
*
* @param int|null $height
*/
public function setScreenLineHeight(int $height = null);

View File

@ -64,7 +64,7 @@ class ConsoleCommandSender implements CommandSender{
/**
* @param Plugin $plugin
* @param string $name
* @param bool $value
* @param bool $value
*
* @return PermissionAttachment
*/

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{
@ -193,9 +191,9 @@ class SimpleCommandMap implements CommandMap{
/**
* @param Command $command
* @param bool $isAlias
* @param string $fallbackPrefix
* @param string $label
* @param bool $isAlias
* @param string $fallbackPrefix
* @param string $label
*
* @return bool
*/
@ -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

@ -143,7 +143,8 @@ class DataPropertyManager{
*/
public function getItem(int $key) : ?Item{
$value = $this->getPropertyValue($key, Entity::DATA_TYPE_SLOT);
assert($value instanceof Item or $value === null);
assert($value instanceof Item or $value === null);
return $value;
}

View File

@ -81,6 +81,7 @@ class EffectInstance{
/**
* @param int $duration
*
* @throws \InvalidArgumentException
*
* @return $this

View File

@ -206,34 +206,38 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_INTERESTED = 26;
public const DATA_FLAG_CHARGED = 27;
public const DATA_FLAG_TAMED = 28;
public const DATA_FLAG_LEASHED = 29;
public const DATA_FLAG_SHEARED = 30;
public const DATA_FLAG_GLIDING = 31;
public const DATA_FLAG_ELDER = 32;
public const DATA_FLAG_MOVING = 33;
public const DATA_FLAG_BREATHING = 34;
public const DATA_FLAG_CHESTED = 35;
public const DATA_FLAG_STACKABLE = 36;
public const DATA_FLAG_SHOWBASE = 37;
public const DATA_FLAG_REARING = 38;
public const DATA_FLAG_VIBRATING = 39;
public const DATA_FLAG_IDLING = 40;
public const DATA_FLAG_EVOKER_SPELL = 41;
public const DATA_FLAG_CHARGE_ATTACK = 42;
public const DATA_FLAG_WASD_CONTROLLED = 43;
public const DATA_FLAG_CAN_POWER_JUMP = 44;
public const DATA_FLAG_LINGER = 45;
public const DATA_FLAG_HAS_COLLISION = 46;
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 47;
public const DATA_FLAG_FIRE_IMMUNE = 48;
public const DATA_FLAG_DANCING = 49;
public const DATA_FLAG_ENCHANTED = 50;
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 51; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
public const DATA_FLAG_CONTAINER_PRIVATE = 52; //inventory is private, doesn't drop contents when killed if true
//53 TransformationComponent
public const DATA_FLAG_SPIN_ATTACK = 54;
public const DATA_FLAG_SWIMMING = 55;
public const DATA_FLAG_BRIBED = 56; //dolphins have this set when they go to find treasure for the player
public const DATA_FLAG_ORPHANED = 29;
public const DATA_FLAG_LEASHED = 30;
public const DATA_FLAG_SHEARED = 31;
public const DATA_FLAG_GLIDING = 32;
public const DATA_FLAG_ELDER = 33;
public const DATA_FLAG_MOVING = 34;
public const DATA_FLAG_BREATHING = 35;
public const DATA_FLAG_CHESTED = 36;
public const DATA_FLAG_STACKABLE = 37;
public const DATA_FLAG_SHOWBASE = 38;
public const DATA_FLAG_REARING = 39;
public const DATA_FLAG_VIBRATING = 40;
public const DATA_FLAG_IDLING = 41;
public const DATA_FLAG_EVOKER_SPELL = 42;
public const DATA_FLAG_CHARGE_ATTACK = 43;
public const DATA_FLAG_WASD_CONTROLLED = 44;
public const DATA_FLAG_CAN_POWER_JUMP = 45;
public const DATA_FLAG_LINGER = 46;
public const DATA_FLAG_HAS_COLLISION = 47;
public const DATA_FLAG_AFFECTED_BY_GRAVITY = 48;
public const DATA_FLAG_FIRE_IMMUNE = 49;
public const DATA_FLAG_DANCING = 50;
public const DATA_FLAG_ENCHANTED = 51;
public const DATA_FLAG_SHOW_TRIDENT_ROPE = 52; // tridents show an animated rope when enchanted with loyalty after they are thrown and return to their owner. To be combined with DATA_OWNER_EID
public const DATA_FLAG_CONTAINER_PRIVATE = 53; //inventory is private, doesn't drop contents when killed if true
//54 TransformationComponent
public const DATA_FLAG_SPIN_ATTACK = 55;
public const DATA_FLAG_SWIMMING = 56;
public const DATA_FLAG_BRIBED = 57; //dolphins have this set when they go to find treasure for the player
public const DATA_FLAG_PREGNANT = 58;
public const DATA_FLAG_LAYING_EGG = 59;
//60 ??
public static $entityCount = 1;
/** @var Entity[] */
@ -260,8 +264,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
Entity::registerEntity(Snowball::class, false, ['Snowball', 'minecraft:snowball']);
Entity::registerEntity(SplashPotion::class, false, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']);
Entity::registerEntity(Squid::class, false, ['Squid', 'minecraft:squid']);
Entity::registerEntity(Villager::class, false, ['Villager', 'minecraft:villager']);
Entity::registerEntity(Zombie::class, false, ['Zombie', 'minecraft:zombie']);
Entity::registerEntity(Villager::class, false, ['Villager', 'minecraft:villager']);
Entity::registerEntity(Zombie::class, false, ['Zombie', 'minecraft:zombie']);
Entity::registerEntity(Human::class, true);
@ -342,7 +346,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
*
* @return CompoundTag
*/
public static function createBaseNBT(Vector3 $pos, ?Vector3 $motion = null , float $yaw = 0.0, float $pitch = 0.0) : CompoundTag{
public static function createBaseNBT(Vector3 $pos, ?Vector3 $motion = null, float $yaw = 0.0, float $pitch = 0.0) : CompoundTag{
return new CompoundTag("", [
new ListTag("Pos", [
new DoubleTag("", $pos->x),
@ -408,8 +412,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public $boundingBox;
/** @var bool */
public $onGround;
/** @var int */
protected $age = 0;
/** @var float */
public $eyeHeight = null;
@ -514,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");
}
@ -561,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();
@ -903,7 +905,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;
}
@ -917,7 +919,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;
}
@ -934,6 +936,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* Called to tick entities while dead. Returns whether the entity should be flagged for despawn yet.
*
* @param int $tickDiff
*
* @return bool
*/
protected function onDeathUpdate(int $tickDiff) : bool{
@ -1041,7 +1044,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
}
$this->age += $tickDiff;
$this->ticksLived += $tickDiff;
return $hasUpdate;
@ -1148,34 +1150,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{
@ -1393,7 +1391,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
Timings::$timerEntityBaseTick->stopTiming();
$this->timings->stopTiming();
//if($this->isStatic())
@ -1807,7 +1804,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);
@ -1840,7 +1837,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;
}
@ -1873,7 +1871,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;
}
@ -1899,7 +1898,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;
}
@ -1952,7 +1952,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);
@ -1963,7 +1963,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);
}
@ -2025,7 +2025,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();
@ -2074,6 +2074,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* Wrapper around {@link Entity#getDataFlag} for generic data flag reading.
*
* @param int $flagId
*
* @return bool
*/
public function getGenericFlag(int $flagId) : bool{

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;
}
@ -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;
@ -841,7 +842,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_ADD;
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->getName(), 0, $this->skin)];
$pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->skin)];
$player->dataPacket($pk);
}
@ -888,6 +889,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* Wrapper around {@link Entity#getDataFlag} for player-specific data flag reading.
*
* @param int $flagId
*
* @return bool
*/
public function getPlayerFlag(int $flagId) : bool{

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;
}
@ -329,6 +330,7 @@ abstract class Living extends Entity implements Damageable{
/**
* Sends the mob's potion effects to the specified player.
*
* @param Player $player
*/
public function sendPotionEffects(Player $player) : void{
@ -621,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);
}
@ -654,11 +657,11 @@ abstract class Living extends Entity implements Damageable{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->doEffectsTick($tickDiff)){
$hasUpdate = true;
}
if($this->isAlive()){
if($this->doEffectsTick($tickDiff)){
$hasUpdate = true;
}
if($this->isInsideOfSolid()){
$hasUpdate = true;
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1);
@ -771,6 +774,7 @@ abstract class Living extends Entity implements Damageable{
/**
* Sets the number of air ticks left in the entity's air supply.
*
* @param int $ticks
*/
public function setAirSupplyTicks(int $ticks) : void{
@ -787,6 +791,7 @@ abstract class Living extends Entity implements Damageable{
/**
* Sets the maximum amount of air ticks the air supply can hold.
*
* @param int $ticks
*/
public function setMaxAirSupplyTicks(int $ticks) : void{

View File

@ -88,6 +88,9 @@ class ExperienceOrb extends Entity{
public $gravity = 0.04;
public $drag = 0.02;
/** @var int */
protected $age = 0;
/**
* @var int
* Ticker used for determining interval in which to look for new target players.
@ -159,6 +162,7 @@ class ExperienceOrb extends Entity{
public function entityBaseTick(int $tickDiff = 1) : bool{
$hasUpdate = parent::entityBaseTick($tickDiff);
$this->age += $tickDiff;
if($this->age > 6000){
$this->flagForDespawn();
return true;

View File

@ -71,7 +71,7 @@ class FallingBlock extends Entity{
$this->block = BlockFactory::get($blockId, $damage);
$this->propertyManager->setInt(self::DATA_VARIANT, BlockFactory::toStaticRuntimeId($this->block->getId(), $this->block->getDamage()));
$this->propertyManager->setInt(self::DATA_VARIANT, $this->block->getRuntimeId());
}
public function canCollideWith(Entity $entity) : bool{
@ -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

@ -53,6 +53,9 @@ class ItemEntity extends Entity{
public $canCollide = false;
/** @var int */
protected $age = 0;
protected function initEntity() : void{
parent::initEntity();
@ -70,9 +73,12 @@ class ItemEntity extends Entity{
}
$this->item = Item::nbtDeserialize($itemTag);
if($this->item->isNull()){
throw new \UnexpectedValueException("Item for " . get_class($this) . " is invalid");
}
$this->server->getPluginManager()->callEvent(new ItemSpawnEvent($this));
(new ItemSpawnEvent($this))->call();
}
public function entityBaseTick(int $tickDiff = 1) : bool{
@ -82,16 +88,16 @@ class ItemEntity extends Entity{
$hasUpdate = parent::entityBaseTick($tickDiff);
if(!$this->isFlaggedForDespawn()){
if($this->pickupDelay > 0 and $this->pickupDelay < 32767){ //Infinite delay
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
}
if(!$this->isFlaggedForDespawn() and $this->pickupDelay > -1 and $this->pickupDelay < 32767){ //Infinite delay
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
}
$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{
@ -99,7 +105,6 @@ class ItemEntity extends Entity{
$hasUpdate = true;
}
}
}
return $hasUpdate;
@ -204,11 +209,12 @@ class ItemEntity extends Entity{
$item = $this->getItem();
$playerInventory = $player->getInventory();
if(!($item instanceof Item) or ($player->isSurvival() and !$playerInventory->canAddItem($item))){
if($player->isSurvival() and !$playerInventory->canAddItem($item)){
return;
}
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($playerInventory, $this));
$ev = new InventoryPickupItemEvent($playerInventory, $this);
$ev->call();
if($ev->isCancelled()){
return;
}

View File

@ -73,6 +73,7 @@ class PaintingMotive{
/**
* @param string $name
*
* @return PaintingMotive|null
*/
public static function getMotiveByName(string $name) : ?PaintingMotive{

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

@ -61,6 +61,9 @@ class Arrow extends Projectile{
/** @var float */
protected $punchKnockback = 0.0;
/** @var int */
protected $collideTicks = 0;
public function __construct(Level $level, CompoundTag $nbt, ?Entity $shootingEntity = null, bool $critical = false){
parent::__construct($level, $nbt, $shootingEntity);
$this->setCritical($critical);
@ -70,12 +73,14 @@ class Arrow extends Projectile{
parent::initEntity();
$this->pickupMode = $this->namedtag->getByte(self::TAG_PICKUP, self::PICKUP_ANY, true);
$this->collideTicks = $this->namedtag->getShort("life", $this->collideTicks);
}
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setByte(self::TAG_PICKUP, $this->pickupMode, true);
$this->namedtag->setShort("life", $this->collideTicks);
}
public function isCritical() : bool{
@ -116,9 +121,14 @@ class Arrow extends Projectile{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->age > 1200){
$this->flagForDespawn();
$hasUpdate = true;
if($this->isCollided){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();
$hasUpdate = true;
}
}else{
$this->collideTicks = 0;
}
return $hasUpdate;
@ -176,7 +186,7 @@ class Arrow extends Projectile{
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return;
}

View File

@ -66,7 +66,5 @@ class EnderPearl extends Throwable{
$owner->attack(new EntityDamageEvent($owner, EntityDamageEvent::CAUSE_FALL, 5));
}
$this->flagForDespawn();
}
}

View File

@ -42,7 +42,5 @@ class ExperienceBottle extends Throwable{
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_GLASS);
$this->level->dropExperience($this, mt_rand(3, 11));
$this->flagForDespawn();
}
}

View File

@ -74,7 +74,6 @@ abstract class Projectile extends Entity{
$this->setMaxHealth(1);
$this->setHealth(1);
$this->age = $this->namedtag->getShort("Age", $this->age);
$this->damage = $this->namedtag->getDouble("damage", $this->damage);
do{
@ -144,7 +143,6 @@ abstract class Projectile extends Entity{
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setShort("Age", $this->age);
$this->namedtag->setDouble("damage", $this->damage);
if($this->blockHit !== null){
@ -243,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){
@ -317,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,8 +27,8 @@ use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\entity\EffectInstance;
use pocketmine\entity\Living;
use pocketmine\event\entity\ProjectileHitEntityEvent;
use pocketmine\event\entity\ProjectileHitBlockEvent;
use pocketmine\event\entity\ProjectileHitEntityEvent;
use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\Potion;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
@ -124,8 +124,6 @@ class SplashPotion extends Throwable{
}
}
}
$this->flagForDespawn();
}
/**
@ -153,6 +151,7 @@ class SplashPotion extends Throwable{
/**
* Sets whether this splash potion will create an area-effect-cloud when it lands.
*
* @param bool $value
*/
public function setLinger(bool $value = true) : void{

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\entity\projectile;
use pocketmine\block\Block;
use pocketmine\math\RayTraceResult;
abstract class Throwable extends Projectile{
public $width = 0.25;
@ -31,18 +34,8 @@ abstract class Throwable extends Projectile{
protected $gravity = 0.03;
protected $drag = 0.01;
public function entityBaseTick(int $tickDiff = 1) : bool{
if($this->closed){
return false;
}
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->age > 1200 or $this->isCollided){
$this->flagForDespawn();
$hasUpdate = true;
}
return $hasUpdate;
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
parent::onHitBlock($blockHit, $hitResult);
$this->flagForDespawn();
}
}

View File

@ -31,6 +31,7 @@ abstract class ExperienceUtils{
* Calculates and returns the amount of XP needed to get from level 0 to level $level
*
* @param int $level
*
* @return int
*/
public static function getXpToReachLevel(int $level) : int{

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

@ -40,8 +40,8 @@ class FurnaceBurnEvent extends BlockEvent implements Cancellable{
/**
* @param Furnace $furnace
* @param Item $fuel
* @param int $burnTime
* @param Item $fuel
* @param int $burnTime
*/
public function __construct(Furnace $furnace, Item $fuel, int $burnTime){
parent::__construct($furnace->getBlock());

View File

@ -38,8 +38,8 @@ class FurnaceSmeltEvent extends BlockEvent implements Cancellable{
/**
* @param Furnace $furnace
* @param Item $source
* @param Item $result
* @param Item $source
* @param Item $result
*/
public function __construct(Furnace $furnace, Item $source, Item $result){
parent::__construct($furnace->getBlock());

View File

@ -63,6 +63,7 @@ class PlayerChangeSkinEvent extends PlayerEvent implements Cancellable{
/**
* @param Skin $skin
*
* @throws \InvalidArgumentException if the specified skin is not valid
*/
public function setNewSkin(Skin $skin) : void{

View File

@ -34,7 +34,7 @@ class PlayerMoveEvent extends PlayerEvent implements Cancellable{
private $to;
/**
* @param Player $player
* @param Player $player
* @param Location $from
* @param Location $to
*/

View File

@ -34,7 +34,7 @@ class DataPacketSendEvent extends ServerEvent implements Cancellable{
private $player;
/**
* @param Player $player
* @param Player $player
* @param DataPacket $packet
*/
public function __construct(Player $player, DataPacket $packet){

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

@ -384,6 +384,7 @@ abstract class BaseInventory implements Inventory{
/**
* Removes the inventory window from all players currently viewing it.
*
* @param bool $force Force removal of permanent windows such as the player's own inventory. Used internally.
*/
public function removeAllViewers(bool $force = false) : void{
@ -398,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

@ -244,6 +244,7 @@ interface Inventory{
* Returns whether the specified slot exists in the inventory.
*
* @param int $slot
*
* @return bool
*/
public function slotExists(int $slot) : bool;

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());
@ -87,6 +88,7 @@ class PlayerInventory extends BaseInventory{
/**
* @param int $slot
*
* @throws \InvalidArgumentException
*/
private function throwIfNotHotbarSlot(int $slot){
@ -99,6 +101,7 @@ class PlayerInventory extends BaseInventory{
* Returns the item in the specified hotbar slot.
*
* @param int $hotbarSlot
*
* @return Item
*
* @throws \InvalidArgumentException if the hotbar slot index is out of range
@ -159,6 +162,7 @@ class PlayerInventory extends BaseInventory{
/**
* Sends the currently-held item to specified targets.
*
* @param Player|Player[] $target
*/
public function sendHeldItem($target){

View File

@ -46,11 +46,11 @@ class ShapedRecipe implements CraftingRecipe{
* Array of 1, 2, or 3 strings representing the rows of the recipe.
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
* @param Item[] $ingredients <br>
* @param Item[] $ingredients <br>
* Char => Item map of items to be set into the shape.
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
* @param Item[] $results List of items that this recipe produces when crafted.
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*/

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;
}
@ -54,6 +55,7 @@ class DropItemAction extends InventoryAction{
* Drops the target item in front of the player.
*
* @param Player $source
*
* @return bool
*/
public function execute(Player $source) : bool{

View File

@ -53,6 +53,7 @@ abstract class Armor extends Durable{
/**
* Sets the dyed colour of this armour piece. This generally only applies to leather armour.
*
* @param Color $color
*/
public function setCustomColor(Color $color) : void{

View File

@ -79,7 +79,7 @@ class Bow extends Tool{
$entity->setBaseDamage($entity->getBaseDamage() + (($powerLevel + 1) / 2));
}
if($this->hasEnchantment(Enchantment::FLAME)){
$entity->setOnFire($entity->getFireTicks() * 20 + 100);
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
}
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
@ -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

@ -57,9 +57,10 @@ class Bucket extends Item implements Consumable{
if($blockClicked instanceof Liquid and $blockClicked->getDamage() === 0){
$stack = clone $this;
$resultItem = $stack->pop();
$resultItem->setDamage($blockClicked->getFlowingForm()->getId());
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $blockReplace, $face, $this, $resultItem));
$stack->pop();
$resultItem = ItemFactory::get(Item::BUCKET, $blockClicked->getFlowingForm()->getId());
$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,9 +81,8 @@ class Bucket extends Item implements Consumable{
}
}
}elseif($resultBlock instanceof Liquid and $blockReplace->canBeReplaced()){
$resultItem = clone $this;
$resultItem->setDamage(0);
$player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $blockReplace, $face, $this, $resultItem));
$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

@ -47,17 +47,17 @@ class ChorusFruit extends Food{
}
public function onConsume(Living $consumer){
$level = $consumer->getLevel();
assert($level !== null);
$minX = $consumer->getFloorX() - 8;
$minY = $consumer->getFloorY() - 8;
$minY = min($consumer->getFloorY(), $consumer->getLevel()->getWorldHeight()) - 8;
$minZ = $consumer->getFloorZ() - 8;
$maxX = $minX + 16;
$maxY = $minY + 16;
$maxZ = $minZ + 16;
$level = $consumer->getLevel();
assert($level !== null);
for($attempts = 0; $attempts < 16; ++$attempts){
$x = mt_rand($minX, $maxX);
$y = mt_rand($minY, $maxY);

View File

@ -38,6 +38,7 @@ abstract class Durable extends Item{
/**
* Sets whether the item will take damage when used.
*
* @param bool $value
*/
public function setUnbreakable(bool $value = true){
@ -46,6 +47,7 @@ abstract class Durable extends Item{
/**
* Applies damage to the item.
*
* @param int $amount
*
* @return bool if any damage was applied to the item

View File

@ -523,6 +523,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param string $name
*
* @return NamedTag|null
*/
public function getNamedTagEntry(string $name) : ?NamedTag{
@ -557,6 +558,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Sets the Item's NBT from the supplied CompoundTag object.
*
* @param CompoundTag $tag
*
* @return Item
@ -589,6 +591,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param int $count
*
* @return Item
*/
public function setCount(int $count) : Item{
@ -669,6 +672,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param int $meta
*
* @return Item
*/
public function setDamage(int $meta) : Item{
@ -779,6 +783,7 @@ class Item implements ItemIds, \JsonSerializable{
* Returns whether the item was changed, for example count decrease or durability change.
*
* @param Player $player
*
* @return bool
*/
public function onReleaseUsing(Player $player) : bool{
@ -844,6 +849,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Returns whether the specified item stack has the same ID, damage, NBT and count as this item stack.
*
* @param Item $other
*
* @return bool
@ -888,6 +894,7 @@ class Item implements ItemIds, \JsonSerializable{
* Returns an Item from properties created in an array by {@link Item#jsonSerialize}
*
* @param array $data
*
* @return Item
*/
final public static function jsonDeserialize(array $data) : Item{
@ -956,7 +963,12 @@ class Item implements ItemIds, \JsonSerializable{
if($idTag instanceof ShortTag){
$item = ItemFactory::get($idTag->getValue(), $meta, $count);
}elseif($idTag instanceof StringTag){ //PC item save format
$item = ItemFactory::fromString($idTag->getValue());
try{
$item = ItemFactory::fromString($idTag->getValue());
}catch(\InvalidArgumentException $e){
//TODO: improve error handling
return ItemFactory::get(Item::AIR, 0, 0);
}
$item->setDamage($meta);
$item->setCount($count);
}else{

View File

@ -37,67 +37,68 @@ class PaintingItem extends Item{
}
public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{
if(!$blockClicked->isTransparent() and $face > 1 and !$blockReplace->isSolid()){
/** @var PaintingMotive[] $motives */
$motives = [];
if($face === Vector3::SIDE_DOWN or $face === Vector3::SIDE_UP){
return false;
}
$totalDimension = 0;
foreach(PaintingMotive::getAll() as $motive){
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
if($currentTotalDimension < $totalDimension){
continue;
$motives = [];
$totalDimension = 0;
foreach(PaintingMotive::getAll() as $motive){
$currentTotalDimension = $motive->getHeight() + $motive->getWidth();
if($currentTotalDimension < $totalDimension){
continue;
}
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
if($currentTotalDimension > $totalDimension){
$totalDimension = $currentTotalDimension;
/*
* This drops all motive possibilities smaller than this
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
* when there is an L-shape of space available.
*/
$motives = [];
}
if(Painting::canFit($player->level, $blockReplace, $face, true, $motive)){
if($currentTotalDimension > $totalDimension){
$totalDimension = $currentTotalDimension;
/*
* This drops all motive possibilities smaller than this
* We use the total of height + width to allow equal chance of horizontal/vertical paintings
* when there is an L-shape of space available.
*/
$motives = [];
}
$motives[] = $motive;
}
$motives[] = $motive;
}
}
if(empty($motives)){ //No space available
return false;
}
if(empty($motives)){ //No space available
return false;
}
/** @var PaintingMotive $motive */
$motive = $motives[array_rand($motives)];
/** @var PaintingMotive $motive */
$motive = $motives[array_rand($motives)];
static $directions = [
Vector3::SIDE_SOUTH => 0,
Vector3::SIDE_WEST => 1,
Vector3::SIDE_NORTH => 2,
Vector3::SIDE_EAST => 3
];
static $directions = [
Vector3::SIDE_SOUTH => 0,
Vector3::SIDE_WEST => 1,
Vector3::SIDE_NORTH => 2,
Vector3::SIDE_EAST => 3
];
$direction = $directions[$face] ?? -1;
if($direction === -1){
return false;
}
$direction = $directions[$face] ?? -1;
if($direction === -1){
return false;
}
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
$nbt->setByte("Direction", $direction);
$nbt->setString("Motive", $motive->getName());
$nbt->setInt("TileX", $blockClicked->getFloorX());
$nbt->setInt("TileY", $blockClicked->getFloorY());
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
$nbt = Entity::createBaseNBT($blockReplace, null, $direction * 90, 0);
$nbt->setByte("Direction", $direction);
$nbt->setString("Motive", $motive->getName());
$nbt->setInt("TileX", $blockClicked->getFloorX());
$nbt->setInt("TileY", $blockClicked->getFloorY());
$nbt->setInt("TileZ", $blockClicked->getFloorZ());
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
$entity = Entity::createEntity("Painting", $blockReplace->getLevel(), $nbt);
if($entity instanceof Entity){
--$this->count;
$entity->spawnToAll();
if($entity instanceof Entity){
--$this->count;
$entity->spawnToAll();
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
return true;
}
$player->getLevel()->broadcastLevelEvent($blockReplace->add(0.5, 0.5, 0.5), LevelEventPacket::EVENT_SOUND_ITEMFRAME_PLACE); //item frame and painting have the same sound
return true;
}
return false;

View File

@ -71,9 +71,8 @@ class Potion extends Item implements Consumable{
* Returns a list of effects applied by potions with the specified ID.
*
* @param int $id
* @return EffectInstance[]
*
* @throws \InvalidArgumentException if the potion type is unknown
* @return EffectInstance[]
*/
public static function getPotionEffectsById(int $id) : array{
switch($id){
@ -213,7 +212,7 @@ class Potion extends Item implements Consumable{
];
}
throw new \InvalidArgumentException("Unknown potion type $id");
return [];
}
public function __construct(int $meta = 0){

View File

@ -27,8 +27,8 @@ use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\Player;
abstract class ProjectileItem extends Item{
@ -58,7 +58,8 @@ 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{

View File

@ -137,7 +137,7 @@ class WritableBook extends Item{
/**
* Inserts a new page with the given text and moves other pages upwards.
*
* @param int $pageId
* @param int $pageId
* @param string $pageText
*
* @return bool indicating success

View File

@ -69,6 +69,7 @@ class EnchantmentInstance{
/**
* Sets the level of the enchantment.
*
* @param int $level
*
* @return $this

View File

@ -62,6 +62,7 @@ class ProtectionEnchantment extends Enchantment{
/**
* Returns the base EPF this enchantment type offers for the given enchantment level.
*
* @param int $level
*
* @return int
@ -72,6 +73,7 @@ class ProtectionEnchantment extends Enchantment{
/**
* Returns whether this enchantment type offers protection from the specified damage source's cause.
*
* @param EntityDamageEvent $event
*
* @return bool

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

@ -241,12 +241,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);
}
@ -284,6 +292,7 @@ class Level implements ChunkManager, Metadatable{
/**
* @param string $str
*
* @return int
*/
public static function getDifficultyFromString(string $str) : int{
@ -444,7 +453,7 @@ class Level implements ChunkManager, Metadatable{
if($players === null){
foreach($pk as $e){
$this->addChunkPacket($sound->getFloorX() >> 4, $sound->getFloorZ() >> 4, $e);
$this->broadcastPacketToViewers($sound, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
@ -459,7 +468,7 @@ class Level implements ChunkManager, Metadatable{
if($players === null){
foreach($pk as $e){
$this->addChunkPacket($particle->getFloorX() >> 4, $particle->getFloorZ() >> 4, $e);
$this->broadcastPacketToViewers($particle, $e);
}
}else{
$this->server->batchPackets($players, $pk, false);
@ -479,10 +488,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);
}
}
@ -504,7 +513,7 @@ class Level implements ChunkManager, Metadatable{
$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{
@ -523,8 +532,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);
@ -532,7 +545,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(true);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if(!$force and $ev->isCancelled()){
return false;
@ -583,6 +596,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.
@ -600,7 +623,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
*/
@ -692,7 +735,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
@ -727,7 +779,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();
}
@ -764,6 +817,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){
@ -811,8 +866,6 @@ class Level implements ChunkManager, Metadatable{
}
$this->chunkPackets = [];
$this->timings->doTick->stopTiming();
}
public function checkSleep(){
@ -872,16 +925,12 @@ class Level implements ChunkManager, Metadatable{
$pk->z = $b->z;
if($b instanceof Block){
$blockId = $b->getId();
$blockData = $b->getDamage();
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$blockId = $fullBlock >> 4;
$blockData = $fullBlock & 0xf;
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($blockId, $blockData);
$pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE;
$packets[] = $pk;
@ -898,16 +947,12 @@ class Level implements ChunkManager, Metadatable{
$pk->z = $b->z;
if($b instanceof Block){
$blockId = $b->getId();
$blockData = $b->getDamage();
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$blockId = $fullBlock >> 4;
$blockData = $fullBlock & 0xf;
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($blockId, $blockData);
$pk->flags = $flags;
$packets[] = $pk;
@ -979,13 +1024,20 @@ class Level implements ChunkManager, Metadatable{
foreach($this->chunkTickList as $index => $loaders){
Level::getXZ($index, $chunkX, $chunkZ);
if(($chunk = $this->chunks[$index] ?? null) === null){
unset($this->chunkTickList[$index]);
continue;
}elseif($loaders <= 0){
for($cx = -1; $cx <= 1; ++$cx){
for($cz = -1; $cz <= 1; ++$cz){
if(!isset($this->chunks[Level::chunkHash($chunkX + $cx, $chunkZ + $cz)])){
unset($this->chunkTickList[$index]);
goto skip_to_next; //no "continue 3" thanks!
}
}
}
if($loaders <= 0){
unset($this->chunkTickList[$index]);
}
$chunk = $this->chunks[$index];
foreach($chunk->getEntities() as $entity){
$entity->scheduleUpdate();
}
@ -1014,6 +1066,8 @@ class Level implements ChunkManager, Metadatable{
}
}
}
skip_to_next: //dummy label to break out of nested loops
}
if($this->clearChunksOnTick){
@ -1036,7 +1090,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();
@ -1417,22 +1471,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();
}
@ -1462,13 +1515,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.
@ -1495,7 +1565,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);
}
@ -1528,7 +1598,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();
@ -1691,7 +1762,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(!$canBreak);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1776,12 +1847,12 @@ class Level implements ChunkManager, Metadatable{
}
if($player !== null){
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, $blockClicked->getId() === 0 ? PlayerInteractEvent::RIGHT_CLICK_AIR : PlayerInteractEvent::RIGHT_CLICK_BLOCK);
$ev = new PlayerInteractEvent($player, $item, $blockClicked, $clickVector, $face, PlayerInteractEvent::RIGHT_CLICK_BLOCK);
if($this->checkSpawnProtection($player, $blockClicked)){
$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;
@ -1853,7 +1924,7 @@ class Level implements ChunkManager, Metadatable{
$ev->setCancelled(!$canPlace);
}
$this->server->getPluginManager()->callEvent($ev);
$ev->call();
if($ev->isCancelled()){
return false;
}
@ -1864,7 +1935,7 @@ class Level implements ChunkManager, Metadatable{
}
if($playSound){
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, BlockFactory::toStaticRuntimeId($hand->getId(), $hand->getDamage()));
$this->broadcastLevelSoundEvent($hand, LevelSoundEventPacket::SOUND_PLACE, 1, $hand->getRuntimeId());
}
$item->pop();
@ -2106,6 +2177,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);
@ -2140,6 +2214,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);
@ -2278,6 +2355,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.
*
@ -2315,7 +2404,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);
@ -2394,6 +2483,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
@ -2443,7 +2543,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){
@ -2665,7 +2765,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));
@ -2719,7 +2819,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();
@ -2786,7 +2887,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()){
@ -2977,6 +3078,7 @@ class Level implements ChunkManager, Metadatable{
$this->unloadChunkRequest($X, $Z, true);
}
}
$chunk->collectGarbage();
}
$this->provider->doGarbageCollection();

View File

@ -51,7 +51,7 @@ class Chunk{
/** @var bool */
protected $isInit = false;
/** @var bool*/
/** @var bool */
protected $lightPopulated = false;
/** @var bool */
protected $terrainGenerated = false;
@ -327,8 +327,8 @@ class Chunk{
/**
* Returns the Y coordinate of the highest non-air block at the specified X/Z chunk block coordinates
*
* @param int $x 0-15
* @param int $z 0-15
* @param int $x 0-15
* @param int $z 0-15
*
* @return int 0-255, or -1 if there are no blocks in the column
*/
@ -366,6 +366,7 @@ class Chunk{
/**
* Returns the heightmap value at the specified X/Z chunk block coordinates
*
* @param int $x 0-15
* @param int $z 0-15
* @param int $value
@ -465,6 +466,7 @@ class Chunk{
/**
* Returns a column of block IDs from bottom to top at the specified X/Z chunk block coordinates.
*
* @param int $x 0-15
* @param int $z 0-15
*
@ -475,11 +477,13 @@ class Chunk{
foreach($this->subChunks as $subChunk){
$result .= $subChunk->getBlockIdColumn($x, $z);
}
return $result;
}
/**
* Returns a column of block meta values from bottom to top at the specified X/Z chunk block coordinates.
*
* @param int $x 0-15
* @param int $z 0-15
*
@ -495,6 +499,7 @@ class Chunk{
/**
* Returns a column of sky light values from bottom to top at the specified X/Z chunk block coordinates.
*
* @param int $x 0-15
* @param int $z 0-15
*
@ -510,6 +515,7 @@ class Chunk{
/**
* Returns a column of block light values from bottom to top at the specified X/Z chunk block coordinates.
*
* @param int $x 0-15
* @param int $z 0-15
*
@ -830,18 +836,17 @@ class Chunk{
}
/**
* Disposes of empty subchunks
* Disposes of empty subchunks and frees data where possible
*/
public function pruneEmptySubChunks(){
public function collectGarbage() : void{
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
}elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
$this->subChunks[$y] = $this->emptySubChunk;
}else{
continue; //do not set changed
if($subChunk instanceof SubChunk){
if($subChunk->isEmpty()){
$this->subChunks[$y] = $this->emptySubChunk;
}else{
$subChunk->collectGarbage();
}
}
$this->hasChanged = true;
}
}
@ -858,8 +863,8 @@ class Chunk{
$result .= $this->subChunks[$y]->networkSerialize();
}
$result .= pack("v*", ...$this->heightMap)
. $this->biomeIds
. chr(0); //border block array count
. $this->biomeIds
. chr(0); //border block array count
//Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client.
foreach($this->tiles as $tile){
@ -881,20 +886,31 @@ class Chunk{
$stream = new BinaryStream();
$stream->putInt($this->x);
$stream->putInt($this->z);
$count = 0;
$subChunks = "";
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
$stream->putByte(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0));
if($this->terrainGenerated){
//subchunks
$count = 0;
$subChunks = "";
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
}
++$count;
$subChunks .= chr($y) . $subChunk->getBlockIdArray() . $subChunk->getBlockDataArray();
if($this->lightPopulated){
$subChunks .= $subChunk->getBlockSkyLightArray() . $subChunk->getBlockLightArray();
}
}
$stream->putByte($count);
$stream->put($subChunks);
//biomes
$stream->put($this->biomeIds);
if($this->lightPopulated){
$stream->put(pack("v*", ...$this->heightMap));
}
++$count;
$subChunks .= chr($y) . $subChunk->fastSerialize();
}
$stream->putByte($count);
$stream->put($subChunks);
$stream->put(pack("v*", ...$this->heightMap) .
$this->biomeIds .
chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0)));
return $stream->getBuffer();
}
@ -910,19 +926,36 @@ class Chunk{
$x = $stream->getInt();
$z = $stream->getInt();
$flags = $stream->getByte();
$lightPopulated = (bool) ($flags & 4);
$terrainPopulated = (bool) ($flags & 2);
$terrainGenerated = (bool) ($flags & 1);
$subChunks = [];
$count = $stream->getByte();
for($y = 0; $y < $count; ++$y){
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
$biomeIds = "";
$heightMap = [];
if($terrainGenerated){
$count = $stream->getByte();
for($y = 0; $y < $count; ++$y){
$subChunks[$stream->getByte()] = new SubChunk(
$stream->get(4096), //blockids
$stream->get(2048), //blockdata
$lightPopulated ? $stream->get(2048) : "", //skylight
$lightPopulated ? $stream->get(2048) : "" //blocklight
);
}
$biomeIds = $stream->get(256);
if($lightPopulated){
$heightMap = array_values(unpack("v*", $stream->get(512)));
}
}
$heightMap = array_values(unpack("v*", $stream->get(512)));
$biomeIds = $stream->get(256);
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
$flags = $stream->getByte();
$chunk->lightPopulated = (bool) ($flags & 4);
$chunk->terrainPopulated = (bool) ($flags & 2);
$chunk->terrainGenerated = (bool) ($flags & 1);
$chunk->setGenerated($terrainGenerated);
$chunk->setPopulated($terrainPopulated);
$chunk->setLightPopulated($lightPopulated);
return $chunk;
}
}

View File

@ -126,8 +126,4 @@ class EmptySubChunk implements SubChunkInterface{
public function networkSerialize() : string{
return "\x00" . str_repeat("\x00", 6144);
}
public function fastSerialize() : string{
throw new \BadMethodCallException("Should not try to serialize empty subchunks");
}
}

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,13 +101,12 @@ class SubChunk implements SubChunkInterface{
if($data !== null){
$i >>= 1;
$byte = ord($this->data{$i});
if(($y & 1) === 0){
$this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f));
}else{
$this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f));
}
if($this->data{$i} !== $byte){
$shift = ($y & 1) << 2;
$oldPair = ord($this->data{$i});
$newPair = ($oldPair & ~(0xf << $shift)) | (($data & 0xf) << $shift);
if($newPair !== $oldPair){
$this->data{$i} = chr($newPair);
$changed = true;
}
}
@ -121,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;
}
@ -222,24 +204,24 @@ class SubChunk implements SubChunkInterface{
return "\x00" . $this->ids . $this->data;
}
public function fastSerialize() : string{
return
$this->ids .
$this->data .
$this->skyLight .
$this->blockLight;
}
public static function fastDeserialize(string $data) : SubChunk{
return new SubChunk(
substr($data, 0, 4096), //ids
substr($data, 4096, 2048), //data
substr($data, 6144, 2048), //sky light
substr($data, 8192, 2048) //block light
);
}
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

@ -27,6 +27,7 @@ interface SubChunkInterface{
/**
* @param bool $checkLight
*
* @return bool
*/
public function isEmpty(bool $checkLight = true) : bool;
@ -201,9 +202,4 @@ interface SubChunkInterface{
* @return string
*/
public function networkSerialize() : string;
/**
* @return string
*/
public function fastSerialize() : string;
}

View File

@ -29,7 +29,6 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\FullChunkDataPacket;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\tile\Spawnable;
class ChunkRequestTask extends AsyncTask{
@ -39,43 +38,29 @@ class ChunkRequestTask extends AsyncTask{
protected $chunkX;
protected $chunkZ;
protected $tiles;
protected $compressionLevel;
public function __construct(Level $level, int $chunkX, int $chunkZ, Chunk $chunk){
$this->levelId = $level->getId();
$this->compressionLevel = $level->getServer()->networkCompressionLevel;
$this->chunk = $chunk->fastSerialize();
$this->chunk = $chunk->networkSerialize();
$this->chunkX = $chunkX;
$this->chunkZ = $chunkZ;
//TODO: serialize tiles with chunks
$tiles = "";
foreach($chunk->getTiles() as $tile){
if($tile instanceof Spawnable){
$tiles .= $tile->getSerializedSpawnCompound();
}
}
$this->tiles = $tiles;
}
public function onRun(){
$chunk = Chunk::fastDeserialize($this->chunk);
$pk = new FullChunkDataPacket();
$pk->chunkX = $this->chunkX;
$pk->chunkZ = $this->chunkZ;
$pk->data = $chunk->networkSerialize() . $this->tiles;
$pk->data = $this->chunk;
$batch = new BatchPacket();
$batch->addPacket($pk);
$batch->setCompressionLevel($this->compressionLevel);
$batch->encode();
$this->setResult($batch->buffer, false);
$this->setResult($batch->buffer);
}
public function onCompletion(Server $server){

View File

@ -148,6 +148,7 @@ interface LevelProvider{
/**
* Sets the world difficulty.
*
* @param int $difficulty
*/
public function setDifficulty(int $difficulty);

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