Compare commits

...

280 Commits
3.1.7 ... 3.3.3

Author SHA1 Message Date
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
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
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
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
545ec9c881 Updated PreProcessor submodule 2018-11-26 14:02:23 +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
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
e018311e73 Make start script errors a bit more noob-friendly 2018-11-13 18:23:54 +00:00
71d02e5870 Improve dev build error messages 2018-11-12 22:07:14 +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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
af5637e050 PlayerListEntry: remove dead fields 2018-10-16 22:56:21 +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
78923177f9 VersionString: use appropriate regex for number matching 2018-10-16 16:46:27 +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
97980d4516 Update composer dependency versions 2018-10-12 09:31:19 +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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
53a76c0d14 Merge branch 'release/3.1' into release/3.2 2018-09-11 11:35:38 +01:00
5af4dd20df Merge branch 'release/3.1' into release/3.2 2018-09-08 14:25:11 +01:00
d8e27e6081 Bow: fix wrong arithmetic for Flame fire ticks, closes #2420 2018-09-06 19:30:55 +01:00
14a2ffa51b Merge branch 'release/3.1' into release/3.2 2018-09-06 18:43:28 +01:00
9f4722f537 3.2.3 is next 2018-09-04 11:56:27 +01:00
cb04f287eb Release 3.2.2 2018-09-04 11:51:28 +01:00
f649ef5195 Sync 3.2 and 3.1 branches 2018-09-04 11:51:05 +01:00
56ee957fda 3.2.2 is next 2018-08-31 19:03:58 +01:00
1193efd69e Release 3.2.1 2018-08-31 18:58:53 +01:00
f466fd5568 Updated runtime IDs table
apparently 1 (!) missing entry (for barrier) was causing client sided crashes unexplained. This is infuriating.
2018-08-31 16:19:42 +01:00
d5a5209334 Merge branch 'release/3.1' into release/3.2 2018-08-30 15:46:46 +01:00
bca493a682 3.2.1 is next 2018-08-30 11:04:47 +01:00
ba12dfafd6 Release 3.2.0 2018-08-29 16:28:35 +01:00
e09087de26 Fix version numbers 2018-08-29 16:27:01 +01:00
888dba704b Merge branch 'mcpe-1.6' into release/3.2 2018-08-29 16:23:53 +01:00
511249c562 Sync 3.2 and 3.1 branches 2018-08-29 16:23:28 +01:00
05d9bb45d0 Merge branch 'release/3.1' into release/3.2 2018-08-26 19:15:16 +01:00
e5cda34548 Merge branch 'release/3.1' into mcpe-1.6 2018-08-26 18:05:14 +01:00
032b20f659 Server: remove premature optimization of findEntity() 2018-08-25 18:37:18 +01:00
fe6d546190 Merge branch 'release/3.1' into release/3.2 2018-08-25 17:49:21 +01:00
3e6f70ddf6 Merge branch 'release/3.1' into mcpe-1.6 2018-08-23 18:00:06 +01:00
bea634a9b7 Change air tank regeneration to match UA (#2396) 2018-08-22 19:10:53 +01:00
8daf3dc8b4 Merge branch 'release/3.1' into release/3.2 2018-08-22 15:41:52 +01:00
d419d4308f Add a couple of item constants, register Scute item
leaving out turtle helmet for now because of complications relating to the effect application - I REALLY don't want to tick armour if I can avoid it, due to the performance concerns.
2018-08-21 19:54:24 +01:00
9ca38ba868 Protocol changes for 1.6.0.8 + resource packs "fix" 2018-08-21 17:36:55 +01:00
424c50e1e9 Protocol changes for 1.6.0.5, minus Entity->Actor rename 2018-08-21 17:36:29 +01:00
566f3c6262 AvailableCommandsPacket: stricter decode and handling 2018-08-21 17:14:54 +01:00
0d05dcec08 AvailableCommandsPacket: deal with dynamic enums
somehow I missed this, thanks @NiclasOlofsson for pointing it out
2018-08-21 17:14:53 +01:00
986077e03c Protocol changes for 1.6.0.1 2018-08-21 17:14:53 +01:00
ddcb2f002a Tile: Be explicit about not calling Tile::createNBT() (#2388)
A common pitfall developers fall into with this function is that it has to be called from the scope of the tile class you're creating NBT for, but people commonly do Tile::createNBT() directly, which then results in cryptic "Tile is not registered" errors. This now throws a BadMethodCallException instead to be fully clear about this.

In the future this will be removed completely once NBT is no longer required to create a tile, but for now this is a confusing issue that should be dealt with.
2018-08-19 19:54:22 +01:00
c496480d2b Merge branch 'release/3.1' into release/3.2 2018-08-19 19:52:40 +01:00
2eda8cfad3 Merge branch 'release/3.1' into release/3.2 2018-08-19 16:01:47 +01:00
21e7b5ea43 TesterPlugin: removed usage of deprecated ServerCommandEvent 2018-08-19 14:05:26 +01:00
8304675af7 Merge branch 'release/3.1' into release/3.2 2018-08-19 13:59:58 +01:00
ebffff0caa Added CommandEvent, deprecated (Remote)?ServerCommandEvent (#2376) 2018-08-19 12:59:16 +01:00
0dc4bd36e1 Form: change handleResponse() return type to void
this returning is counter intuitive and doesn't make any sense without the queuing mechanism. Instead it's simpler to just use Player->sendForm().
2018-08-19 11:44:59 +01:00
9d17c9a09d Merge branch 'release/3.1' into release/3.2 2018-08-19 11:22:58 +01:00
bfa415e108 Add unit tests for json decoding hack 2018-08-19 10:30:52 +01:00
b66095cb36 Added a hack for MC W10 JSON empty strings bug (#2383) 2018-08-19 10:10:40 +01:00
0336ae8229 Merge branch 'release/3.1' into release/3.2 2018-08-17 19:47:41 +01:00
4b3e17e681 Sync 3.2 and 3.1 branches 2018-08-16 18:33:54 +01:00
5424644ca1 Merge branch 'release/3.1' into release/3.2 2018-08-16 15:52:19 +01:00
aa7c4bc64d Merge branch 'release/3.1' into release/3.2 2018-08-15 13:48:46 +01:00
df8e10cad9 Forms API, part 1: add Player->sendForm() and Form interface
There's no implementation here yet, but that can come later. This lays the ground for allowing plugins to have an integrated method to send forms, as well as a solution to the ID conflict problem.

A built in implementation should not be a concretion and it should be able to be swapped for third party implementations. This enables the possiblity to do so.
2018-08-14 16:06:55 +01:00
d98a6e566c Merge branch 'release/3.1' into release/3.2 2018-08-14 15:03:30 +01:00
ade2be9eee Merge branch 'release/3.1' into release/3.2 2018-08-13 13:22:40 +01:00
39ed6a7cdf Merge branch 'release/3.1' into release/3.2 2018-08-11 19:59:53 +01:00
bec5aaa54b Merge branch 'release/3.1' into release/3.2 2018-08-11 19:37:18 +01:00
cf29ab1f17 Arrow: remove unused import 2018-08-07 19:06:40 +01:00
c5c5a53a13 Merge branch 'release/3.1' into release/3.2 2018-08-07 18:49:13 +01:00
63a65680ac typo 2018-08-07 14:43:29 +01:00
47cd6fe105 EntityDamageEvent: Add API to customize Living entity attack cooldown time
closes #2310
2018-08-07 14:39:26 +01:00
f582b5a3db Merge branch 'release/3.1' into release/3.2 2018-08-07 14:32:38 +01:00
7f0fa2ac3d PluginBase: Do not fill defaults from resources/config.yml (#2316)
This fixes #2219.
2018-08-07 12:33:24 +01:00
max
f3b2bcfd13 Added Conduit Power effect 2018-08-06 21:30:15 +01:00
c947909c2e Updated language submodule 2018-08-06 20:52:53 +01:00
09dadc72bc Merge branch 'release/3.1' into release/3.2 2018-08-06 18:45:08 +01:00
ca541032ae Move Player death message derivation to PlayerDeathEvent static method
This now no longer requires a Player to operate, only a player name.
2018-08-05 12:19:16 +01:00
bcf9915082 Merge branch 'release/3.1' into release/3.2 2018-08-05 11:33:11 +01:00
8d6dc4e188 Merge branch 'release/3.1' into release/3.2 2018-08-04 16:47:26 +01:00
12d8d925c8 TimingsCommand: check for instances of InternetException only 2018-08-04 14:59:31 +01:00
f3f229ef7c Internet: only catch InternetExceptions - anything else is an unexpected fault condition 2018-08-04 14:51:26 +01:00
6614183c7f Merge branch 'release/3.1' into release/3.2 2018-08-03 20:07:52 +01:00
7ebf3c7bf4 Merge branch 'release/3.1' into release/3.2 2018-08-03 18:50:22 +01:00
334caaaa34 Merge branch 'release/3.1' into release/3.2 2018-08-03 18:24:44 +01:00
6fcaef068f Merge branch 'release/3.1' into release/3.2 2018-08-02 14:43:51 +01:00
c09ad9263b Empty merge of 3.1 into 3.2 2018-07-30 15:21:42 +01:00
4cc2f037a9 Merge branch 'release/3.1' into release/3.2 2018-07-30 14:54:10 +01:00
99045fe21a Entity: Implement setting score tag 2018-07-30 09:36:32 +01:00
bda271ca63 Merge branch 'release/3.1' into release/3.2 2018-07-27 11:47:36 +01:00
b3f2396ea5 UPnP: Make error message less useless 2018-07-26 16:00:35 +01:00
ab0510cb37 Merge branch 'release/3.1' into release/3.2 2018-07-26 14:40:33 +01:00
9a423be1db Internet: Throw more specific exceptions
RuntimeException is very generic and might be thrown for other reasons apart from web request failures.

This is backwards compatible because InternetException is a descendent of RuntimeException. Additionally, getURL() and postURL() have intentionally been left untouched for backwards compatibility's sake.
2018-07-26 12:34:14 +01:00
08be51dc23 Clear permissions on server reload 2018-07-26 10:40:28 +01:00
94352782d5 https://media.giphy.com/media/UAUtB4Oi9U4EM/giphy.gif 2018-07-26 10:31:57 +01:00
8fae79f85b Merge branch 'release/3.1' into release/3.2 2018-07-26 10:25:19 +01:00
9a2845640b Permissions management cleanup (#2332)
* Added a new PermissionManager, remove ridiculous cyclic dependencies of Permissions on Server

Aside from all the other ridiculous design problems with the permission system, the biggest problems are its API. This is, once again, a result of poor API design copied from Bukkit.

This pull request removes all permission-related functionality from `PluginManager` and moves it to the `pocketmine\permission\PermissionManager` class.

As can be observed from the removed code in the diff, the permissions system was previously entirely dependent on the Server, because it needed to get the PluginManager for registering permissions. This is utterly ridiculous. This refactor isolates _most_ permission-related functionality within the `permission` namespace.

As mentioned above, this stupid API is a direct result of copying from Bukkit. If you look at the API documentation for Bukkit for `PluginManager` you will see that the methods I'm deprecating here are also in there.

## Changes
- Added a new `PermissionManager` class. This can be accessed via its singleton `getInstance()` static method.
- Deprecated the following `PluginManager` methods - these will be removed no later than 4.0.0:
  - `getPermission()`
  - `addPermission()`
  - `removePermission()`
  - `getDefaultPermissions()`
  - `recalculatePermissionDefaults()`
  - `subscribeToPermission()`
  - `unsubscribeFromPermission()`
  - `getPermissionSubscriptions()`
  - `subscribeToDefaultPerms()`
  - `unsubscribeFromDefaultPerms()`
  - `getDefaultPermSubscriptions()`
  - `getPermissions()`
2018-07-26 10:21:41 +01:00
580f71d496 Permission: cosmetic reorg 2018-07-25 20:56:23 +01:00
24f11779f2 Level: don't try to unregister generators from non-live workers
this was causing garbage-collected workers to get restarted on shutdown if they previously had the generator registered for that level.
2018-07-25 18:52:17 +01:00
706c620d04 Move Internet-related functions from Utils into their own class (#2324)
- Added `Internet::getIP()`, `Internet::getURL()`, `Internet::postURL()`, and `Internet::simpleCurl()`.
- Deprecated the corresponding functions in `Utils`. Updating to the new functions is as simple as replacing `Utils` with `Internet`, since this doesn't break backwards compatibility.

The deprecations should be catered for by plugin developers. These deprecated redirects will be removed no later than 4.0.0.
2018-07-25 15:51:18 +01:00
951870e6ec Merge branch 'release/3.1' into release/3.2 2018-07-25 15:30:40 +01:00
1405099768 Merge branch 'release/3.1' into release/3.2 2018-07-24 17:19:18 +01:00
1464487945 Utils: remove unnecessary do...while(false) from getIP() 2018-07-24 16:33:03 +01:00
40c28f4d26 PluginManager: Automatically create data directories for plugins (#2284) 2018-07-21 15:57:37 +01:00
90bf94f8f7 Merge branch 'release/3.1' into release/3.2 2018-07-21 09:53:41 +01:00
522ef042a7 yet another empty merge... this is tiresome 2018-07-20 12:21:49 +01:00
76ee6bc298 Merge branch 'release/3.1' into release/3.2 2018-07-20 11:57:13 +01:00
04f20c703c Merge branch 'release/3.1' into release/3.2 2018-07-20 11:30:39 +01:00
efe4b0cd3a Merge branch 'release/3.1' into release/3.2 2018-07-18 15:14:34 +01:00
527d8e9374 Merge branch 'release/3.1' into release/3.2 2018-07-17 18:35:16 +01:00
c1c70a8a98 move up a version
pushing this back to 3.2 so that we can make space for MCPE 1.5 line as 3.1.
2018-07-17 18:34:55 +01:00
b7f15b6574 Merge branch 'release/3.0' into release/3.1 2018-07-17 16:56:57 +01:00
6ab2fa84da added some tests for ItemFactory::fromString() 2018-07-17 14:52:47 +01:00
b480c63060 Fixed ItemFactory::fromString() meta handling bug introduced by 71c3c34976 2018-07-17 14:46:08 +01:00
f6b54f5116 Server: don't create levels inside catch-all
Under normal circumstances, none of the boxed code will throw exceptions. Under exceptional circumstances, the caller should know about it. Usually the caller is the server. We don't want to catch unexpected exceptions because those should crash the server and generate a crashdump.
2018-07-17 12:18:46 +01:00
89bfc380e3 Merge branch 'release/3.0' into release/3.1 2018-07-17 12:14:33 +01:00
40030e9800 added some LevelProviderManager tests 2018-07-17 12:02:08 +01:00
ad1cf38c21 LevelProviderManager: tighten up checks on registering 2018-07-17 12:02:08 +01:00
5d769147ca LevelProviderManager: make addProvider() throw InvalidArgumentException instead of LevelException
LevelException is not useful because it's too generic.
2018-07-17 12:02:08 +01:00
6f00a30ad7 Merge branch 'release/3.0' into release/3.1 2018-07-17 10:12:52 +01:00
b4bf6901e3 Server: remove useless try/catch around Query event firing
this doesn't raise any exceptions, and if it causes Errors to be thrown, those are defects that should be fixed. A catch-all is a bad thing.
2018-07-17 10:10:28 +01:00
71c3c34976 ItemFactory: prepare for handling items with negative IDs 2018-07-16 13:24:12 +01:00
16c253d7a9 Item: allow negative IDs
this will be needed in the future for extended blocks support.
2018-07-16 12:46:16 +01:00
7efe767f1f Merge branch 'release/3.0' into release/3.1 2018-07-16 12:08:22 +01:00
2e18fe710c MemoryManager: Shut down idle workers during GC to reclaim memory
workers can be a major memory hog, especially if you have lots of them.
2018-07-14 18:07:37 +01:00
878dd3b842 Merge branch 'release/3.0' into release/3.1 2018-07-14 16:06:02 +01:00
478a131aa5 Flat: separate logic of parsing preset and generating base chunk
now always generates the base chunk on init, because the generator won't be created unless there is generation to be done.
2018-07-14 11:51:49 +01:00
53068caf3c Level: Only register generators when attempting to actually generate chunks
This saves a ton of memory on servers which don't generate any chunks during their runtime (which is most servers).
2018-07-14 11:34:55 +01:00
fe7ad7a5b3 Merge branch 'release/3.0' into release/3.1 2018-07-14 10:39:28 +01:00
24f749a933 Merge branch 'release/3.0' into release/3.1 2018-07-13 12:36:10 +01:00
af80aefd45 Remove async config save (#2298)
As discussed in #2297:

Honestly I don't see a fit purpose for async saving at all. It should either always be synchronous or always asynchronous, and at the user's own option. However, this isn't currently possible because Config doesn't enable you to get the serialized content without writing it to disk.

Consider the following code:
```php
		for($i = 0, $size = $this->getServer()->getAsyncPool()->getSize(); $i < $size; ++$i){
			$this->getServer()->getAsyncPool()->submitTask(new class extends AsyncTask{
				public function onRun(){
					sleep(5);
				}
			});
		}
		$config = $this->getConfig();
		$config->set("steve", "hi");
		$config->save(true);
		$config->set("steve", "bye");
		$config->save(false);
```
Output:
```yml
---
steve: hi
...
```
Expected output:
```yml
---
steve: bye
...
```

Additionally, if your configs are causing you performance issues when you're saving, it's a clear sign that
a) you're saving too much
b) you're abusing configs and should consider using a database.

Configs should be used for _simple_ data which does not change much. Configuration is such that the _user_ is expected to be able to modify it. As such, it should never be an issue to save synchronously.

In the future, something like ReactPHP may be introduced to allow proper async saving. When this happens, async saving would always be sequential but non blocking. Using threads for this makes no sense.
2018-07-12 19:31:00 +01:00
1d5c741f28 PluginBase: Automatically save default config if it doesn't exist (#2285)
I wasn't sure whether this would be considered a bug fix or a feature. Nonetheless, it's a behavioural change, so it belongs in 3.1 if anywhere.

Prior to this, plugins would be required to call saveDefaultConfig() before calling getConfig() or anything else. Calling getConfig() without saveDefaultConfig() first would generate an empty configuration file. Instead, it now saves the default config before loading it.
2018-07-12 19:25:48 +01:00
3a373b880d Listener: Add documentation on functionality (#2292)
The Listener interface is one of the most magical parts of PocketMine-MP, and before this pull request it didn't have a single bit of documentation.
2018-07-12 19:24:46 +01:00
1b7cd156aa Merge branch 'release/3.0' into release/3.1 2018-07-12 18:04:19 +01:00
7a164a8254 PluginManager: Allow @ignoreCancelled annotation on event handlers to not have parameters (#2294) 2018-07-12 17:12:14 +01:00
066c990301 Merge branch 'release/3.0' into release/3.1 2018-07-11 10:21:16 +01:00
287ff8d7bf Merge branch 'release/3.0' into release/3.1 2018-07-11 09:15:19 +01:00
1087212d75 Merge branch 'release/3.0' into release/3.1 2018-07-10 12:48:02 +01:00
0c350f2f57 Add quitMessage parameter to Player::kick() 2018-07-09 18:40:30 +01:00
bfcef2ab6b Add setReason() method to PlayerKickEvent 2018-07-09 18:36:19 +01:00
2994d0f3ae Merge branch 'release/3.0' into release/3.1 2018-07-09 10:06:28 +01:00
57cc0ebe75 Merge branch 'release/3.0' into release/3.1 2018-07-08 12:17:06 +01:00
7554d9a370 Empty merge 2018-07-07 19:22:30 +01:00
32574118ea Implemented Mending enchantment (#2257) 2018-07-06 13:28:33 +01:00
5a3135659b Merge branch 'release/3.0' into release/3.1 2018-07-06 13:12:13 +01:00
b90d7d1839 Merge branch 'release/3.0' into release/3.1 2018-07-05 17:43:11 +01:00
670b940837 PocketMine.php: clean up on platform dependency checks 2018-07-05 17:32:13 +01:00
6cad7be3ef Merge branch 'release/3.0' into release/3.1 2018-07-05 12:11:12 +01:00
28a72a93b4 Chunk: Use an SplFixedArray for heightmap
this goes on 3.1 because it changes the behaviour of chunk cloning, which might possibly break some plugins, and this isn't a bug fix.

This should see no change in behaviour other than a minor performance improvement and slight reduction in memory usage.
2018-07-05 11:58:20 +01:00
0f0d12bebc Merge branch 'release/3.0' into release/3.1 2018-07-02 16:58:15 +01:00
dfc11abf2d Level: fixed sendBlocks() documentation 2018-07-02 16:53:48 +01:00
17eef9f902 Level: stricten type checks on sendBlocks()
because people are morons
2018-07-02 16:53:00 +01:00
b04319a4ab Merge branch 'release/3.0' into release/3.1 2018-06-29 12:30:52 +01:00
0afbf6c547 Merge branch 'release/3.0' into release/3.1 2018-06-29 12:19:25 +01:00
ec2cca04a7 Merge branch 'release/3.0' into release/3.1 2018-06-24 17:34:37 +01:00
272b76d24c fix Punch mess 2018-06-24 13:43:52 +01:00
8c672cb7c8 Implemented Sharpness, Fire Aspect and Knockback enchantments 2018-06-24 12:13:54 +01:00
4d9368f205 Merge branch 'release/3.0' into release/3.1 2018-06-24 12:07:45 +01:00
97c267c70c Implemented Punch enchantment 2018-06-23 17:40:01 +01:00
85a3c0e7dc Implemented Flame enchantment 2018-06-23 17:11:20 +01:00
2f70a1eefb Implemented Thorns enchantment (#2258)
This implementation is rough and can probably be improved to make it extendable, but this works for now and can be improved later.
2018-06-23 13:36:58 +01:00
7ba6e92b6c Merge branch 'release/3.0' into release/3.1 2018-06-23 13:04:05 +01:00
47c862bc38 Projectile: check for blockhit change on nearby blockupdate 2018-06-23 12:57:13 +01:00
860c20109b TNT can now be ignited by burning arrows 2018-06-23 12:54:21 +01:00
1c0b49343c Implemented Infinity enchantment (#2259) 2018-06-23 11:44:35 +01:00
814a949580 Implemented Power enchantment 2018-06-23 11:39:39 +01:00
b393f5f17e Projectile: ensure that damage multiplier gets saved and restored 2018-06-23 10:41:04 +01:00
f1970492c1 Projectile: added API to modify projectile base damage multiplier
This adds two new methods:
- Projectile->getBaseDamage()
- Projectile->setBaseDamage()
2018-06-23 10:38:58 +01:00
4c9ca53b32 Merge branch 'release/3.0' into release/3.1 2018-06-22 17:47:33 +01:00
390db976e5 Arrow: allow controlling pickup mode (like PC)
This allows controlling how arrows are picked up:
- by anything
- by only creative players
- by nothing

This adds new API methods to Arrow:
- getPickupMode()
- setPickupMode()

This adds new public constants to Arrow:
- PICKUP_NONE
- PICKUP_ANY
- PICKUP_CREATIVE
2018-06-22 13:40:32 +01:00
98ac534820 bump version 2018-06-22 13:22:11 +01:00
172 changed files with 3199 additions and 1319 deletions

22
composer.lock generated
View File

@ -76,16 +76,16 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.1",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8"
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/a4ee39f313c6870153fb7c7e513b211217f7daf8",
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/474f0cf0a47656d0122b4f3f71302e694ed6977b",
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b",
"shasum": ""
},
"require": {
@ -109,10 +109,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.2",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2018-09-04T10:36:02+00:00"
"time": "2018-10-12T08:26:44+00:00"
},
{
"name": "pocketmine/raklib",
@ -191,16 +191,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,7 +219,7 @@
"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": [],

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

@ -243,6 +243,9 @@ class MemoryManager{
if($this->garbageCollectionAsync){
$pool = $this->server->getAsyncPool();
if(($w = $pool->shutdownUnusedWorkers()) > 0){
$this->server->getLogger()->debug("Shut down $w idle async pool workers");
}
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
}
@ -470,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

@ -32,11 +32,9 @@ use pocketmine\entity\Effect;
use pocketmine\entity\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\Living;
use pocketmine\entity\object\ItemEntity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\Skin;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\inventory\InventoryCloseEvent;
@ -68,6 +66,8 @@ use pocketmine\event\player\PlayerToggleSneakEvent;
use pocketmine\event\player\PlayerToggleSprintEvent;
use pocketmine\event\player\PlayerTransferEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CraftingGrid;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCursorInventory;
@ -77,6 +77,8 @@ use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\MeleeWeaponEnchantment;
use pocketmine\item\Item;
use pocketmine\item\WritableBook;
use pocketmine\item\WrittenBook;
@ -116,6 +118,7 @@ use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
@ -146,6 +149,7 @@ use pocketmine\network\SourceInterface;
use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissionAttachment;
use pocketmine\permission\PermissionAttachmentInfo;
use pocketmine\permission\PermissionManager;
use pocketmine\plugin\Plugin;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\tile\ItemFrame;
@ -169,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
@ -193,9 +198,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*/
protected $sessionAdapter;
/** @var int */
protected $protocol = -1;
/** @var string */
protected $ip;
/** @var int */
@ -322,6 +324,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var int[] ID => ticks map */
protected $usedItemsCooldown = [];
/** @var int */
protected $formIdCounter = 0;
/** @var Form[] */
protected $forms = [];
/**
* @return TranslationContainer|string
*/
@ -621,7 +628,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/**
* @param Plugin $plugin
* @param string $name
* @param bool $value
* @param bool $value
*
* @return PermissionAttachment
*/
@ -637,8 +644,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function recalculatePermissions(){
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$permManager = PermissionManager::getInstance();
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$permManager->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
if($this->perm === null){
return;
@ -647,10 +655,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->perm->recalculatePermissions();
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
if($this->spawned){
@ -1018,10 +1026,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
$this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this,
@ -1319,6 +1327,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{
@ -1839,8 +1848,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
$this->protocol = $packet->protocol;
if($packet->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){
$this->sendPlayStatus(PlayStatusPacket::LOGIN_FAILED_CLIENT, true);
@ -1923,7 +1930,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function sendPlayStatus(int $status, bool $immediate = false){
$pk = new PlayStatusPacket();
$pk->status = $status;
$pk->protocol = $this->protocol;
$this->sendDataPacket($pk, false, $immediate);
}
@ -2031,7 +2037,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
$manager = $this->server->getResourcePackManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById($uuid);
$pack = $manager->getPackById(substr($uuid, 0, strpos($uuid, "_"))); //dirty hack for mojang's dirty hack for versions
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->close("", "disconnectionScreen.resourcePack", true);
@ -2262,6 +2268,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{
@ -2499,6 +2506,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints());
$meleeEnchantmentDamage = 0;
/** @var EnchantmentInstance[] $meleeEnchantments */
$meleeEnchantments = [];
foreach($heldItem->getEnchantments() as $enchantment){
$type = $enchantment->getType();
if($type instanceof MeleeWeaponEnchantment and $type->isApplicableTo($target)){
$meleeEnchantmentDamage += $type->getDamageBonus($enchantment->getLevel());
$meleeEnchantments[] = $enchantment;
}
}
$ev->setModifier($meleeEnchantmentDamage, EntityDamageEvent::MODIFIER_WEAPON_ENCHANTMENTS);
if($cancelled){
$ev->setCancelled();
}
@ -2526,6 +2546,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
foreach($meleeEnchantments as $enchantment){
$type = $enchantment->getType();
assert($type instanceof MeleeWeaponEnchantment);
$type->onPostAttack($this, $target, $enchantment->getLevel());
}
if($this->isAlive()){
//reactive damage like thorns might cause us to be killed by attacking another mob, which
//would mean we'd already have dropped the inventory by the time we reached here
@ -2698,7 +2724,7 @@ 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();
}
@ -2762,7 +2788,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:
@ -2822,6 +2848,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{
@ -3121,7 +3148,7 @@ 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.
@ -3145,14 +3172,16 @@ 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) : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $this->getLeaveMessage()));
public function kick(string $reason = "", bool $isAdmin = true, $quitMessage = null) : bool{
$this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage()));
if(!$ev->isCancelled()){
$reason = $ev->getReason();
$message = $reason;
if($isAdmin){
if(!$this->isBanned()){
@ -3246,7 +3275,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();
@ -3330,6 +3359,48 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->dataPacket($pk);
}
/**
* Sends a Form to the player, or queue to send it if a form is already open.
*
* @param Form $form
*/
public function sendForm(Form $form) : void{
$id = $this->formIdCounter++;
$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;
}
}
/**
* @param int $formId
* @param mixed $responseData
*
* @return bool
*/
public function onFormSubmit(int $formId, $responseData) : bool{
if(!isset($this->forms[$formId])){
$this->server->getLogger()->debug("Got unexpected response for form $formId");
return false;
}
try{
$this->forms[$formId]->handleResponse($this, $responseData);
}catch(FormValidationException $e){
$this->server->getLogger()->critical("Failed to validate form " . get_class($this->forms[$formId]) . ": " . $e->getMessage());
$this->server->getLogger()->logException($e);
}finally{
unset($this->forms[$formId]);
}
return true;
}
/**
* Note for plugin developers: use kick() with the isAdmin
* flag set to kick without the "Kicked by admin" part instead of this method.
@ -3350,8 +3421,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->interface->close($this, $notify ? $reason : "");
$this->sessionAdapter = null;
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
$this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
PermissionManager::getInstance()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
$this->stopSleep();
@ -3444,11 +3515,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");
}
@ -3485,7 +3554,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);
}
}
@ -3500,123 +3569,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
protected function onDeath() : void{
$message = "death.attack.generic";
$params = [
$this->getDisplayName()
];
$cause = $this->getLastDamageCause();
switch($cause === null ? EntityDamageEvent::CAUSE_CUSTOM : $cause->getCause()){
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.player";
$params[] = $e->getDisplayName();
break;
}elseif($e instanceof Living){
$message = "death.attack.mob";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_PROJECTILE:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.arrow";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.arrow";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_SUICIDE:
$message = "death.attack.generic";
break;
case EntityDamageEvent::CAUSE_VOID:
$message = "death.attack.outOfWorld";
break;
case EntityDamageEvent::CAUSE_FALL:
if($cause instanceof EntityDamageEvent){
if($cause->getFinalDamage() > 2){
$message = "death.fell.accident.generic";
break;
}
}
$message = "death.attack.fall";
break;
case EntityDamageEvent::CAUSE_SUFFOCATION:
$message = "death.attack.inWall";
break;
case EntityDamageEvent::CAUSE_LAVA:
$message = "death.attack.lava";
break;
case EntityDamageEvent::CAUSE_FIRE:
$message = "death.attack.onFire";
break;
case EntityDamageEvent::CAUSE_FIRE_TICK:
$message = "death.attack.inFire";
break;
case EntityDamageEvent::CAUSE_DROWNING:
$message = "death.attack.drown";
break;
case EntityDamageEvent::CAUSE_CONTACT:
if($cause instanceof EntityDamageByBlockEvent){
if($cause->getDamager()->getId() === Block::CACTUS){
$message = "death.attack.cactus";
}
}
break;
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
if($cause instanceof EntityDamageByEntityEvent){
$e = $cause->getDamager();
if($e instanceof Player){
$message = "death.attack.explosion.player";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.explosion.player";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}
}else{
$message = "death.attack.explosion";
}
break;
case EntityDamageEvent::CAUSE_MAGIC:
$message = "death.attack.magic";
break;
case EntityDamageEvent::CAUSE_CUSTOM:
break;
default:
break;
}
//Crafting grid must always be evacuated even if keep-inventory is true. This dumps the contents into the
//main inventory and drops the rest on the ground.
$this->doCloseInventory();
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)));
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops()));
if(!$ev->getKeepInventory()){
foreach($ev->getDrops() as $item){

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.1.7";
const BASE_VERSION = "3.3.3";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;
@ -53,83 +53,89 @@ namespace pocketmine {
* Enjoy it as much as I did writing it. I don't want to do it again.
*/
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
critical_error(\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . ".");
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
exit(1);
}
if(PHP_INT_SIZE < 8){
critical_error("Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported.");
critical_error("Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.");
critical_error("Please refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
exit(1);
}
/* Dependencies check */
$errors = 0;
if(php_sapi_name() !== "cli"){
critical_error("You must run " . \pocketmine\NAME . " using the CLI.");
++$errors;
}
$extensions = [
"bcmath" => "BC Math",
"curl" => "cURL",
"ctype" => "ctype",
"date" => "Date",
"hash" => "Hash",
"json" => "JSON",
"mbstring" => "Multibyte String",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
"pthreads" => "pthreads",
"reflection" => "Reflection",
"sockets" => "Sockets",
"spl" => "SPL",
"yaml" => "YAML",
"zip" => "Zip",
"zlib" => "Zlib"
];
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
critical_error("Unable to find the $name ($ext) extension.");
++$errors;
/**
* @return string[]
*/
function check_platform_dependencies(){
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
//If PHP version isn't high enough, anything below might break, so don't bother checking it.
return [
\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . "."
];
}
}
if(extension_loaded("pthreads")){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
$messages = [];
if(PHP_INT_SIZE < 8){
$messages[] = "Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
}
if(version_compare($pthreads_version, "3.1.7dev") < 0){
critical_error("pthreads >= 3.1.7dev is required, while you have $pthreads_version.");
++$errors;
if(php_sapi_name() !== "cli"){
$messages[] = "You must run " . \pocketmine\NAME . " using the CLI.";
}
}
if(extension_loaded("leveldb")){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){
critical_error("php-leveldb >= 0.2.1 is required, while you have $leveldb_version");
++$errors;
$extensions = [
"bcmath" => "BC Math",
"curl" => "cURL",
"ctype" => "ctype",
"date" => "Date",
"hash" => "Hash",
"json" => "JSON",
"mbstring" => "Multibyte String",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
"pthreads" => "pthreads",
"reflection" => "Reflection",
"sockets" => "Sockets",
"spl" => "SPL",
"yaml" => "YAML",
"zip" => "Zip",
"zlib" => "Zlib"
];
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
$messages[] = "Unable to find the $name ($ext) extension.";
}
}
if(extension_loaded("pthreads")){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "3.1.7dev") < 0){
$messages[] = "pthreads >= 3.1.7dev is required, while you have $pthreads_version.";
}
}
if(extension_loaded("leveldb")){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
}
}
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}
return $messages;
}
if(extension_loaded("pocketmine")){
critical_error("The native PocketMine extension is no longer supported.");
++$errors;
}
if($errors > 0){
if(!empty($messages = check_platform_dependencies())){
echo PHP_EOL;
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
critical_error("Selected PHP binary ($binary) does not satisfy some requirements.");
foreach($messages as $m){
echo " - $m" . PHP_EOL;
}
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
echo PHP_EOL;
exit(1);
}
unset($messages);
error_reporting(-1);
@ -139,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

@ -39,6 +39,7 @@ use pocketmine\event\HandlerList;
use pocketmine\event\level\LevelInitEvent;
use pocketmine\event\level\LevelLoadEvent;
use pocketmine\event\player\PlayerDataSaveEvent;
use pocketmine\event\server\CommandEvent;
use pocketmine\event\server\QueryRegenerateEvent;
use pocketmine\event\server\ServerCommandEvent;
use pocketmine\inventory\CraftingManager;
@ -82,6 +83,7 @@ use pocketmine\network\rcon\RCON;
use pocketmine\network\upnp\UPnP;
use pocketmine\permission\BanList;
use pocketmine\permission\DefaultPermissions;
use pocketmine\permission\PermissionManager;
use pocketmine\plugin\PharPluginLoader;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginLoadOrder;
@ -89,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;
@ -99,6 +100,7 @@ use pocketmine\timings\TimingsHandler;
use pocketmine\updater\AutoUpdater;
use pocketmine\utils\Binary;
use pocketmine\utils\Config;
use pocketmine\utils\Internet;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Terminal;
use pocketmine\utils\TextFormat;
@ -802,9 +804,8 @@ 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());
@ -813,11 +814,7 @@ class Server{
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);
@ -988,6 +985,7 @@ class Server{
/**
* @internal
*
* @param Level $level
*/
public function removeLevel(Level $level) : void{
@ -1025,15 +1023,8 @@ class Server{
return false;
}
try{
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
}catch(\Throwable $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()]));
$this->logger->logException($e);
return false;
}
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
@ -1076,21 +1067,15 @@ class Server{
}
}
try{
$path = $this->getDataPath() . "worlds/" . $name . "/";
/** @var LevelProvider $providerClass */
$providerClass::generate($path, $name, $seed, $generator, $options);
$path = $this->getDataPath() . "worlds/" . $name . "/";
/** @var LevelProvider $providerClass */
$providerClass::generate($path, $name, $seed, $generator, $options);
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
$level->setTickRate($this->baseTickRate);
}catch(\Throwable $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.generationError", [$name, $e->getMessage()]));
$this->logger->logException($e);
return false;
}
$level->setTickRate($this->baseTickRate);
$this->getPluginManager()->callEvent(new LevelInitEvent($level));
@ -1148,17 +1133,12 @@ class Server{
* Useful for tracking entities across multiple worlds without needing strong references.
*
* @param int $entityId
* @param Level|null $expectedLevel Level to look in first for the target
* @param Level|null $expectedLevel @deprecated Level to look in first for the target
*
* @return Entity|null
*/
public function findEntity(int $entityId, Level $expectedLevel = null){
$levels = $this->levels;
if($expectedLevel !== null){
array_unshift($levels, $expectedLevel);
}
foreach($levels as $level){
foreach($this->levels as $level){
assert(!$level->isClosed());
if(($entity = $level->getEntity($entityId)) instanceof Entity){
return $entity;
@ -1305,7 +1285,7 @@ class Server{
if(($player = $this->getPlayerExact($name)) !== null){
$player->recalculatePermissions();
}
$this->operators->save(true);
$this->operators->save();
}
/**
@ -1325,7 +1305,7 @@ class Server{
*/
public function addWhitelist(string $name){
$this->whitelist->set(strtolower($name), true);
$this->whitelist->save(true);
$this->whitelist->save();
}
/**
@ -1463,6 +1443,7 @@ class Server{
$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;
}
@ -1638,7 +1619,7 @@ 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);
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
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());
@ -1696,7 +1677,7 @@ class Server{
}
if($this->properties->hasChanged()){
$this->properties->save(true);
$this->properties->save();
}
if(!($this->getDefaultLevel() instanceof Level)){
@ -1747,7 +1728,7 @@ class Server{
if(!is_array($recipients)){
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1773,7 +1754,7 @@ class Server{
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1803,7 +1784,7 @@ class Server{
/** @var Player[] $recipients */
$recipients = [];
foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1828,7 +1809,7 @@ class Server{
/** @var CommandSender[] $recipients */
$recipients = [];
foreach(explode(";", $permissions) as $permission){
foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){
foreach(PermissionManager::getInstance()->getPermissionSubscriptions($permission) as $permissible){
if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){
$recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
}
@ -1953,10 +1934,20 @@ class Server{
*
* @param CommandSender $sender
* @param string $commandLine
* @param bool $internal
*
* @return bool
*/
public function dispatchCommand(CommandSender $sender, string $commandLine) : bool{
public function dispatchCommand(CommandSender $sender, string $commandLine, bool $internal = false) : bool{
if(!$internal){
$this->pluginManager->callEvent($ev = new CommandEvent($sender, $commandLine));
if($ev->isCancelled()){
return false;
}
$commandLine = $ev->getCommand();
}
if($this->commandMap->dispatch($sender, $commandLine)){
return true;
}
@ -1976,6 +1967,7 @@ class Server{
$this->pluginManager->disablePlugins();
$this->pluginManager->clearPlugins();
PermissionManager::getInstance()->clearPermissions();
$this->commandMap->clearCommands();
$this->logger->info("Reloading properties...");
@ -2199,6 +2191,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);
@ -2218,7 +2218,7 @@ class Server{
if($report){
$url = ($this->getProperty("auto-report.use-https", true) ? "https" : "http") . "://" . $this->getProperty("auto-report.host", "crash.pmmp.io") . "/submit/api";
$reply = Utils::postURL($url, [
$reply = Internet::postURL($url, [
"report" => "yes",
"name" => $this->getName() . " " . $this->getPocketMineVersion(),
"email" => "crash@pocketmine.net",
@ -2241,6 +2241,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);
}
@ -2313,7 +2319,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);
}
@ -2336,7 +2342,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);
@ -2352,7 +2358,11 @@ 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;
}
@ -2388,7 +2398,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);
}
@ -2525,13 +2535,9 @@ class Server{
}
if(($this->tickCounter & 0b111111111) === 0){
try{
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
if($this->queryHandler !== null){
$this->queryHandler->regenerateInfo();
}
}catch(\Throwable $e){
$this->logger->logException($e);
$this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5));
if($this->queryHandler !== null){
$this->queryHandler->regenerateInfo();
}
}

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{
@ -421,8 +426,8 @@ class BlockFactory{
public static function registerStaticRuntimeIdMappings() : void{
/** @var mixed[] $runtimeIdMap */
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
foreach($runtimeIdMap as $obj){
self::registerMapping($obj["runtimeID"], $obj["id"], $obj["data"]);
foreach($runtimeIdMap as $k => $obj){
self::registerMapping($k, $obj["id"], $obj["data"]);
}
}

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

@ -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

@ -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

@ -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

@ -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

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\item\FlintSteel;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -56,6 +57,16 @@ class TNT extends Solid{
return false;
}
public function hasEntityCollision() : bool{
return true;
}
public function onEntityCollide(Entity $entity) : void{
if($entity instanceof Arrow and $entity->isOnFire()){
$this->ignite();
}
}
public function ignite(int $fuse = 80){
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), 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

@ -28,6 +28,7 @@ namespace pocketmine\command;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\permission\PermissionManager;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\TextFormat;
@ -293,7 +294,7 @@ abstract class Command{
$m = clone $message;
$result = "[" . $source->getName() . ": " . ($source->getServer()->getLanguage()->get($m->getText()) !== $m->getText() ? "%" : "") . $m->getText() . "]";
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$colored = TextFormat::GRAY . TextFormat::ITALIC . $result;
$m->setText($result);
@ -301,7 +302,7 @@ abstract class Command{
$m->setText($colored);
$colored = clone $m;
}else{
$users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$users = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$result = new TranslationContainer("chat.type.admin", [$source->getName(), $message]);
$colored = new TranslationContainer(TextFormat::GRAY . TextFormat::ITALIC . "%chat.type.admin", [$source->getName(), $message]);
}

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

@ -59,7 +59,7 @@ class FormattedCommandAlias extends Command{
}
foreach($commands as $command){
$result |= Server::getInstance()->dispatchCommand($sender, $command);
$result |= Server::getInstance()->dispatchCommand($sender, $command, true);
}
return (bool) $result;

View File

@ -193,9 +193,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
*/

View File

@ -30,6 +30,7 @@ use pocketmine\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\InternetException;
class TimingsCommand extends VanillaCommand{
@ -131,7 +132,7 @@ class TimingsCommand extends VanillaCommand{
return;
}
$result = $this->getResult()[0];
if($result instanceof \RuntimeException){
if($result instanceof InternetException){
$server->getLogger()->logException($result);
return;
}

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

@ -56,6 +56,7 @@ class Effect{
public const SATURATION = 23;
public const LEVITATION = 24; //TODO
public const FATAL_POISON = 25;
public const CONDUIT_POWER = 26;
/** @var Effect[] */
protected static $effects = [];
@ -86,6 +87,7 @@ class Effect{
self::registerEffect(new Effect(Effect::SATURATION, "%potion.saturation", new Color(0xf8, 0x24, 0x23), false, 1));
self::registerEffect(new Effect(Effect::LEVITATION, "%potion.levitation", new Color(0xce, 0xff, 0xff)));
self::registerEffect(new Effect(Effect::FATAL_POISON, "%potion.poison", new Color(0x4e, 0x93, 0x31), true));
self::registerEffect(new Effect(Effect::CONDUIT_POWER, "%potion.conduitPower", new Color(0x1d, 0xc2, 0xd1)));
}
/**

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),
@ -608,6 +612,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->propertyManager->setByte(self::DATA_ALWAYS_SHOW_NAMETAG, $value ? 1 : 0);
}
/**
* @return string|null
*/
public function getScoreTag() : ?string{
return $this->propertyManager->getString(self::DATA_SCORE_TAG);
}
/**
* @param string $score
*/
public function setScoreTag(string $score) : void{
$this->propertyManager->setString(self::DATA_SCORE_TAG, $score);
}
/**
* @return float
*/
@ -733,7 +751,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function getOwningEntity() : ?Entity{
$eid = $this->getOwningEntityId();
if($eid !== null){
return $this->server->findEntity($eid, $this->level);
return $this->server->findEntity($eid);
}
return null;
@ -773,7 +791,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function getTargetEntity() : ?Entity{
$eid = $this->getTargetEntityId();
if($eid !== null){
return $this->server->findEntity($eid, $this->level);
return $this->server->findEntity($eid);
}
return null;
@ -918,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{
@ -1376,7 +1395,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
Timings::$timerEntityBaseTick->stopTiming();
$this->timings->stopTiming();
//if($this->isStatic())
@ -2057,6 +2075,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

@ -34,6 +34,7 @@ use pocketmine\inventory\EntityInventoryEventProcessor;
use pocketmine\inventory\InventoryHolder;
use pocketmine\inventory\PlayerInventory;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FoodSource;
use pocketmine\item\Item;
@ -518,6 +519,42 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
return $this->xpCooldown === 0;
}
public function onPickupXp(int $xpValue) : void{
static $mainHandIndex = -1;
//TODO: replace this with a more generic equipment getting/setting interface
/** @var Durable[] $equipment */
$equipment = [];
if(($item = $this->inventory->getItemInHand()) instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
$equipment[$mainHandIndex] = $item;
}
//TODO: check offhand
foreach($this->armorInventory->getContents() as $k => $item){
if($item instanceof Durable and $item->hasEnchantment(Enchantment::MENDING)){
$equipment[$k] = $item;
}
}
if(!empty($equipment)){
$repairItem = $equipment[$k = array_rand($equipment)];
if($repairItem->getDamage() > 0){
$repairAmount = min($repairItem->getDamage(), $xpValue * 2);
$repairItem->setDamage($repairItem->getDamage() - $repairAmount);
$xpValue -= (int) ceil($repairAmount / 2);
if($k === $mainHandIndex){
$this->inventory->setItemInHand($repairItem);
}else{
$this->armorInventory->setItem($k, $repairItem);
}
}
}
$this->addXp($xpValue); //this will still get fired even if the value is 0 due to mending, to play sounds
$this->resetXpCooldown();
}
/**
* Sets the duration in ticks until the human can pick up another XP orb.
*
@ -804,7 +841,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);
}
@ -851,6 +888,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

@ -34,6 +34,7 @@ use pocketmine\inventory\ArmorInventory;
use pocketmine\inventory\ArmorInventoryEventProcessor;
use pocketmine\item\Armor;
use pocketmine\item\Consumable;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -328,6 +329,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{
@ -476,6 +478,26 @@ abstract class Living extends Entity implements Damageable{
protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
$this->setAbsorption(max(0, $this->getAbsorption() + $source->getModifier(EntityDamageEvent::MODIFIER_ABSORPTION)));
$this->damageArmor($source->getBaseDamage());
if($source instanceof EntityDamageByEntityEvent){
$damage = 0;
foreach($this->armorInventory->getContents() as $k => $item){
if($item instanceof Armor and ($thornsLevel = $item->getEnchantmentLevel(Enchantment::THORNS)) > 0){
if(mt_rand(0, 99) < $thornsLevel * 15){
$this->damageItem($item, 3);
$damage += ($thornsLevel > 10 ? $thornsLevel - 10 : 1 + mt_rand(0, 3));
}else{
$this->damageItem($item, 1); //thorns causes an extra +1 durability loss even if it didn't activate
}
$this->armorInventory->setItem($k, $item);
}
}
if($damage > 0){
$source->getDamager()->attack(new EntityDamageByEntityEvent($this, $source->getDamager(), EntityDamageEvent::CAUSE_MAGIC, $damage));
}
}
}
/**
@ -490,16 +512,20 @@ abstract class Living extends Entity implements Damageable{
$armor = $this->armorInventory->getContents(true);
foreach($armor as $item){
if($item instanceof Armor){
$item->applyDamage($durabilityRemoved);
if($item->isBroken()){
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
}
$this->damageItem($item, $durabilityRemoved);
}
}
$this->armorInventory->setContents($armor);
}
private function damageItem(Durable $item, int $durabilityRemoved) : void{
$item->applyDamage($durabilityRemoved);
if($item->isBroken()){
$this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_BREAK);
}
}
public function attack(EntityDamageEvent $source) : void{
if($this->attackTime > 0 or $this->noDamageTicks > 0){
$lastCause = $this->getLastDamageCause();
@ -535,7 +561,7 @@ abstract class Living extends Entity implements Damageable{
return;
}
$this->attackTime = 10; //0.5 seconds cooldown
$this->attackTime = $source->getAttackCooldown();
if($source instanceof EntityDamageByEntityEvent){
$e = $source->getDamager();
@ -629,24 +655,19 @@ 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);
$this->attack($ev);
}
if(!$this->canBreathe()){
$this->setBreathing(false);
$this->doAirSupplyTick($tickDiff);
if($this->doAirSupplyTick($tickDiff)){
$hasUpdate = true;
}elseif(!$this->isBreathing()){
$this->setBreathing(true);
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
}
}
@ -675,22 +696,42 @@ abstract class Living extends Entity implements Damageable{
}
/**
* Ticks the entity's air supply when it cannot breathe.
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
*
* @param int $tickDiff
*
* @return bool
*/
protected function doAirSupplyTick(int $tickDiff) : void{
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
lcg_value() <= (1 / ($respirationLevel + 1))
){
$ticks = $this->getAirSupplyTicks() - $tickDiff;
protected function doAirSupplyTick(int $tickDiff) : bool{
$ticks = $this->getAirSupplyTicks();
$oldTicks = $ticks;
if(!$this->canBreathe()){
$this->setBreathing(false);
if($ticks <= -20){
$this->setAirSupplyTicks(0);
$this->onAirExpired();
}else{
$this->setAirSupplyTicks($ticks);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(Enchantment::RESPIRATION)) <= 0 or
lcg_value() <= (1 / ($respirationLevel + 1))
){
$ticks -= $tickDiff;
if($ticks <= -20){
$ticks = 0;
$this->onAirExpired();
}
}
}elseif(!$this->isBreathing()){
if($ticks < ($max = $this->getMaxAirSupplyTicks())){
$ticks += $tickDiff * 5;
}
if($ticks >= $max){
$ticks = $max;
$this->setBreathing(true);
}
}
if($ticks !== $oldTicks){
$this->setAirSupplyTicks($ticks);
}
return $ticks !== $oldTicks;
}
/**
@ -698,7 +739,7 @@ abstract class Living extends Entity implements Damageable{
* @return bool
*/
public function canBreathe() : bool{
return $this->hasEffect(Effect::WATER_BREATHING) or !$this->isUnderwater();
return $this->hasEffect(Effect::WATER_BREATHING) or $this->hasEffect(Effect::CONDUIT_POWER) or !$this->isUnderwater();
}
/**
@ -731,6 +772,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{
@ -747,6 +789,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

@ -147,7 +147,7 @@ class ExperienceOrb extends Entity{
return null;
}
$entity = $this->server->findEntity($this->targetPlayerRuntimeId, $this->level);
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
if($entity instanceof Human){
return $entity;
}
@ -204,10 +204,7 @@ class ExperienceOrb extends Entity{
if($currentTarget->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){
$this->flagForDespawn();
$currentTarget->addXp($this->getXpValue());
$currentTarget->resetXpCooldown();
//TODO: check Mending enchantment
$currentTarget->onPickupXp($this->getXpValue());
}
}

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{

View File

@ -73,6 +73,9 @@ 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));
@ -205,7 +208,7 @@ 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;
}

View File

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

View File

@ -40,13 +40,26 @@ use pocketmine\Player;
class Arrow extends Projectile{
public const NETWORK_ID = self::ARROW;
public const PICKUP_NONE = 0;
public const PICKUP_ANY = 1;
public const PICKUP_CREATIVE = 2;
private const TAG_PICKUP = "pickup"; //TAG_Byte
public $width = 0.25;
public $height = 0.25;
protected $gravity = 0.05;
protected $drag = 0.01;
protected $damage = 2;
/** @var float */
protected $damage = 2.0;
/** @var int */
protected $pickupMode = self::PICKUP_ANY;
/** @var float */
protected $punchKnockback = 0.0;
/** @var int */
protected $collideTicks = 0;
@ -58,11 +71,15 @@ class Arrow extends Projectile{
protected function initEntity() : void{
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);
}
@ -83,6 +100,20 @@ class Arrow extends Projectile{
}
}
/**
* @return float
*/
public function getPunchKnockback() : float{
return $this->punchKnockback;
}
/**
* @param float $punchKnockback
*/
public function setPunchKnockback(float $punchKnockback) : void{
$this->punchKnockback = $punchKnockback;
}
public function entityBaseTick(int $tickDiff = 1) : bool{
if($this->closed){
return false;
@ -113,6 +144,31 @@ class Arrow extends Projectile{
$this->broadcastEntityEvent(EntityEventPacket::ARROW_SHAKE, 7); //7 ticks
}
protected function onHitEntity(Entity $entityHit, RayTraceResult $hitResult) : void{
parent::onHitEntity($entityHit, $hitResult);
if($this->punchKnockback > 0){
$horizontalSpeed = sqrt($this->motion->x ** 2 + $this->motion->z ** 2);
if($horizontalSpeed > 0){
$multiplier = $this->punchKnockback * 0.6 / $horizontalSpeed;
$entityHit->setMotion($entityHit->getMotion()->add($this->motion->x * $multiplier, 0.1, $this->motion->z * $multiplier));
}
}
}
/**
* @return int
*/
public function getPickupMode() : int{
return $this->pickupMode;
}
/**
* @param int $pickupMode
*/
public function setPickupMode(int $pickupMode) : void{
$this->pickupMode = $pickupMode;
}
public function onCollideWithPlayer(Player $player) : void{
if($this->blockHit === null){
return;
@ -125,7 +181,12 @@ class Arrow extends Projectile{
return;
}
$this->server->getPluginManager()->callEvent($ev = new InventoryPickupArrowEvent($playerInventory, $this));
$ev = new InventoryPickupArrowEvent($playerInventory, $this);
if($this->pickupMode === self::PICKUP_NONE or ($this->pickupMode === self::PICKUP_CREATIVE and !$player->isCreative())){
$ev->setCancelled();
}
$this->server->getPluginManager()->callEvent($ev);
if($ev->isCancelled()){
return;
}

View File

@ -46,7 +46,8 @@ abstract class Projectile extends Entity{
public const DATA_SHOOTER_ID = 17;
protected $damage = 0;
/** @var float */
protected $damage = 0.0;
/** @var Vector3|null */
protected $blockHit;
@ -73,6 +74,7 @@ abstract class Projectile extends Entity{
$this->setMaxHealth(1);
$this->setHealth(1);
$this->damage = $this->namedtag->getDouble("damage", $this->damage);
do{
$blockHit = null;
@ -111,6 +113,25 @@ abstract class Projectile extends Entity{
return false;
}
/**
* Returns the base damage applied on collision. This is multiplied by the projectile's speed to give a result
* damage.
*
* @return float
*/
public function getBaseDamage() : float{
return $this->damage;
}
/**
* Sets the base amount of damage applied by the projectile.
*
* @param float $damage
*/
public function setBaseDamage(float $damage) : void{
$this->damage = $damage;
}
/**
* Returns the amount of damage this projectile will deal to the entity it hits.
* @return int
@ -122,6 +143,8 @@ abstract class Projectile extends Entity{
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setDouble("damage", $this->damage);
if($this->blockHit !== null){
$this->namedtag->setInt("tileX", $this->blockHit->x);
$this->namedtag->setInt("tileY", $this->blockHit->y);
@ -137,17 +160,19 @@ abstract class Projectile extends Entity{
return true;
}
public function hasMovementUpdate() : bool{
$parent = parent::hasMovementUpdate();
if($parent and $this->blockHit !== null){
public function onNearbyBlockChange() : void{
if($this->blockHit !== null){
$blockIn = $this->level->getBlockAt($this->blockHit->x, $this->blockHit->y, $this->blockHit->z);
if($blockIn->getId() === $this->blockHitId and $blockIn->getDamage() === $this->blockHitData){
return false;
if($blockIn->getId() !== $this->blockHitId or $blockIn->getDamage() !== $this->blockHitData){
$this->blockHit = $this->blockHitId = $this->blockHitData = null;
}
}
return $parent;
parent::onNearbyBlockChange();
}
public function hasMovementUpdate() : bool{
return $this->blockHit === null and parent::hasMovementUpdate();
}
public function move(float $dx, float $dy, float $dz) : void{

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;
@ -151,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

@ -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

@ -23,6 +23,37 @@ declare(strict_types=1);
namespace pocketmine\event;
use pocketmine\plugin\PluginManager;
/**
* Classes implementing this interface can be registered to receive called Events.
* @see PluginManager::registerEvents()
*
* A function in a Listener class must meet the following criteria to be registered as an event handler:
*
* - MUST be public
* - MUST NOT be static
* - MUST accept EXACTLY ONE class parameter which:
* - MUST be a VALID class extending Event
* - MUST NOT be abstract, UNLESS it has an `@allowHandle` annotation
*
* Event handlers do not have to have any particular name - they are detected using reflection.
* They SHOULD NOT return any values (but this is not currently enforced).
*
* Functions which meet the criteria can have the following annotations in their doc comments:
*
* - `@notHandler`: Marks a function as NOT being an event handler. Only needed if the function meets the above criteria.
* - `@softDepend [PluginName]`: Handler WILL NOT be registered if its event doesn't exist. Useful for soft-depending
* on plugin events. Plugin name is optional.
* Example: `@softDepend SimpleAuth`
* - `@ignoreCancelled`: Cancelled events WILL NOT be passed to this handler.
* - `@priority <PRIORITY>`: Sets the priority at which this event handler will receive events.
* Example: `@priority HIGHEST`
* @see EventPriority for a list of possible options.
*
* Event handlers will receive any instanceof the Event class they have chosen to receive. For example, an
* EntityDamageEvent handler will also receive any subclass of EntityDamageEvent.
*/
interface Listener{
}

View File

@ -51,6 +51,6 @@ class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent{
* @return Entity|null
*/
public function getChild() : ?Entity{
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid, $this->getEntity()->getLevel());
return $this->getEntity()->getLevel()->getServer()->findEntity($this->childEntityEid);
}
}

View File

@ -69,7 +69,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{
* @return Entity|null
*/
public function getDamager() : ?Entity{
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId, $this->getEntity()->getLevel());
return $this->getEntity()->getLevel()->getServer()->findEntity($this->damagerEntityId);
}
/**

View File

@ -38,6 +38,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
public const MODIFIER_ARMOR_ENCHANTMENTS = 6;
public const MODIFIER_CRITICAL = 7;
public const MODIFIER_TOTEM = 8;
public const MODIFIER_WEAPON_ENCHANTMENTS = 9;
public const CAUSE_CONTACT = 0;
public const CAUSE_ENTITY_ATTACK = 1;
@ -68,6 +69,9 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
/** @var float[] */
private $originals;
/** @var int */
private $attackCooldown = 10;
/**
* @param Entity $entity
@ -196,4 +200,24 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{
return true;
}
/**
* Returns the cooldown in ticks before the target entity can be attacked again.
*
* @return int
*/
public function getAttackCooldown() : int{
return $this->attackCooldown;
}
/**
* Sets the cooldown in ticks before the target entity can be attacked again.
*
* NOTE: This value is not used in non-Living entities
*
* @param int $attackCooldown
*/
public function setAttackCooldown(int $attackCooldown) : void{
$this->attackCooldown = $attackCooldown;
}
}

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

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\event\Cancellable;
use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
@ -55,7 +56,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
$this->format = $format;
if($recipients === null){
$this->recipients = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
$this->recipients = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
}else{
$this->recipients = $recipients;
}

View File

@ -23,9 +23,15 @@ declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\block\Block;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityDeathEvent;
use pocketmine\item\Item;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\Player;
class PlayerDeathEvent extends EntityDeathEvent{
@ -37,13 +43,13 @@ class PlayerDeathEvent extends EntityDeathEvent{
private $keepInventory = false;
/**
* @param Player $entity
* @param Item[] $drops
* @param string|TextContainer $deathMessage
* @param Player $entity
* @param Item[] $drops
* @param string|TextContainer|null $deathMessage Null will cause the default vanilla message to be used
*/
public function __construct(Player $entity, array $drops, $deathMessage){
public function __construct(Player $entity, array $drops, $deathMessage = null){
parent::__construct($entity, $drops);
$this->deathMessage = $deathMessage;
$this->deathMessage = $deathMessage ?? self::deriveMessage($entity->getDisplayName(), $entity->getLastDamageCause());
}
/**
@ -81,4 +87,123 @@ class PlayerDeathEvent extends EntityDeathEvent{
public function setKeepInventory(bool $keepInventory) : void{
$this->keepInventory = $keepInventory;
}
/**
* Returns the vanilla death message for the given death cause.
*
* @param string $name
* @param null|EntityDamageEvent $deathCause
*
* @return TranslationContainer
*/
public static function deriveMessage(string $name, ?EntityDamageEvent $deathCause) : TranslationContainer{
$message = "death.attack.generic";
$params = [$name];
switch($deathCause === null ? EntityDamageEvent::CAUSE_CUSTOM : $deathCause->getCause()){
case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.player";
$params[] = $e->getDisplayName();
break;
}elseif($e instanceof Living){
$message = "death.attack.mob";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_PROJECTILE:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.arrow";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.arrow";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}else{
$params[] = "Unknown";
}
}
break;
case EntityDamageEvent::CAUSE_SUICIDE:
$message = "death.attack.generic";
break;
case EntityDamageEvent::CAUSE_VOID:
$message = "death.attack.outOfWorld";
break;
case EntityDamageEvent::CAUSE_FALL:
if($deathCause instanceof EntityDamageEvent){
if($deathCause->getFinalDamage() > 2){
$message = "death.fell.accident.generic";
break;
}
}
$message = "death.attack.fall";
break;
case EntityDamageEvent::CAUSE_SUFFOCATION:
$message = "death.attack.inWall";
break;
case EntityDamageEvent::CAUSE_LAVA:
$message = "death.attack.lava";
break;
case EntityDamageEvent::CAUSE_FIRE:
$message = "death.attack.onFire";
break;
case EntityDamageEvent::CAUSE_FIRE_TICK:
$message = "death.attack.inFire";
break;
case EntityDamageEvent::CAUSE_DROWNING:
$message = "death.attack.drown";
break;
case EntityDamageEvent::CAUSE_CONTACT:
if($deathCause instanceof EntityDamageByBlockEvent){
if($deathCause->getDamager()->getId() === Block::CACTUS){
$message = "death.attack.cactus";
}
}
break;
case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
if($deathCause instanceof EntityDamageByEntityEvent){
$e = $deathCause->getDamager();
if($e instanceof Player){
$message = "death.attack.explosion.player";
$params[] = $e->getDisplayName();
}elseif($e instanceof Living){
$message = "death.attack.explosion.player";
$params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName();
break;
}
}else{
$message = "death.attack.explosion";
}
break;
case EntityDamageEvent::CAUSE_MAGIC:
$message = "death.attack.magic";
break;
case EntityDamageEvent::CAUSE_CUSTOM:
break;
default:
break;
}
return new TranslationContainer($message, $params);
}
}

View File

@ -50,6 +50,13 @@ class PlayerKickEvent extends PlayerEvent implements Cancellable{
$this->reason = $reason;
}
/**
* @param string $reason
*/
public function setReason(string $reason) : void{
$this->reason = $reason;
}
public function getReason() : string{
return $this->reason;
}

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

@ -0,0 +1,73 @@
<?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\event\server;
use pocketmine\command\CommandSender;
use pocketmine\event\Cancellable;
/**
* Called when any CommandSender runs a command, early in the process
*
* You don't want to use this except for a few cases like logging commands,
* blocking commands on certain places, or applying modifiers.
*
* The message DOES NOT contain a slash at the start
*/
class CommandEvent extends ServerEvent implements Cancellable{
/** @var string */
protected $command;
/** @var CommandSender */
protected $sender;
/**
* @param CommandSender $sender
* @param string $command
*/
public function __construct(CommandSender $sender, string $command){
$this->sender = $sender;
$this->command = $command;
}
/**
* @return CommandSender
*/
public function getSender() : CommandSender{
return $this->sender;
}
/**
* @return string
*/
public function getCommand() : string{
return $this->command;
}
/**
* @param string $command
*/
public function setCommand(string $command) : void{
$this->command = $command;
}
}

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

@ -27,6 +27,8 @@ use pocketmine\command\CommandSender;
/**
* This event is called when a command is received over RCON.
*
* @deprecated Use CommandEvent instead.
*/
class RemoteServerCommandEvent extends ServerCommandEvent{

View File

@ -33,6 +33,8 @@ use pocketmine\event\Cancellable;
* blocking commands on certain places, or applying modifiers.
*
* The message DOES NOT contain a slash at the start
*
* @deprecated Use CommandEvent instead.
*/
class ServerCommandEvent extends ServerEvent implements Cancellable{
/** @var string */

View File

@ -0,0 +1,43 @@
<?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\form;
use pocketmine\Player;
/**
* Form implementations must implement this interface to be able to utilize the Player form-sending mechanism.
* There is no restriction on custom implementations other than that they must implement this.
*/
interface Form extends \JsonSerializable{
/**
* Handles a form response from a player.
*
* @param Player $player
* @param mixed $data
*
* @throws FormValidationException if the data could not be processed
*/
public function handleResponse(Player $player, $data) : void;
}

View File

@ -0,0 +1,28 @@
<?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\form;
class FormValidationException extends \RuntimeException{
}

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{

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

@ -87,6 +87,7 @@ class PlayerInventory extends BaseInventory{
/**
* @param int $slot
*
* @throws \InvalidArgumentException
*/
private function throwIfNotHotbarSlot(int $slot){
@ -99,6 +100,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 +161,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

@ -54,6 +54,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

@ -24,9 +24,11 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow as ArrowEntity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\entity\EntityShootBowEvent;
use pocketmine\event\entity\ProjectileLaunchEvent;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\Player;
@ -64,6 +66,21 @@ class Bow extends Tool{
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
if($entity instanceof Projectile){
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
if($entity instanceof ArrowEntity){
if($infinity){
$entity->setPickupMode(ArrowEntity::PICKUP_CREATIVE);
}
if(($punchLevel = $this->getEnchantmentLevel(Enchantment::PUNCH)) > 0){
$entity->setPunchKnockback($punchLevel);
}
}
if(($powerLevel = $this->getEnchantmentLevel(Enchantment::POWER)) > 0){
$entity->setBaseDamage($entity->getBaseDamage() + (($powerLevel + 1) / 2));
}
if($this->hasEnchantment(Enchantment::FLAME)){
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
}
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
if($force < 0.1 or $diff < 5){
@ -80,7 +97,9 @@ class Bow extends Tool{
}else{
$entity->setMotion($entity->getMotion()->multiply($ev->getForce()));
if($player->isSurvival()){
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
if(!$infinity){ //TODO: tipped arrows are still consumed when Infinity is applied
$player->getInventory()->removeItem(ItemFactory::get(Item::ARROW, 0, 1));
}
$this->applyDamage(1);
}

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

@ -196,7 +196,10 @@ class Item implements ItemIds, \JsonSerializable{
* @param string $name
*/
public function __construct(int $id, int $meta = 0, string $name = "Unknown"){
$this->id = $id & 0xffff;
if($id < -0x8000 or $id > 0x7fff){ //signed short range
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
}
$this->id = $id;
$this->setDamage($meta);
$this->name = $name;
}
@ -520,6 +523,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param string $name
*
* @return NamedTag|null
*/
public function getNamedTagEntry(string $name) : ?NamedTag{
@ -554,6 +558,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Sets the Item's NBT from the supplied CompoundTag object.
*
* @param CompoundTag $tag
*
* @return Item
@ -586,6 +591,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param int $count
*
* @return Item
*/
public function setCount(int $count) : Item{
@ -666,6 +672,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* @param int $meta
*
* @return Item
*/
public function setDamage(int $meta) : Item{
@ -776,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{
@ -841,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
@ -885,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{
@ -916,7 +926,7 @@ class Item implements ItemIds, \JsonSerializable{
*/
public function nbtSerialize(int $slot = -1, string $tagName = "") : CompoundTag{
$result = new CompoundTag($tagName, [
new ShortTag("id", Binary::signShort($this->id)),
new ShortTag("id", $this->id),
new ByteTag("Count", Binary::signByte($this->count)),
new ShortTag("Damage", $this->meta)
]);
@ -951,9 +961,14 @@ class Item implements ItemIds, \JsonSerializable{
$idTag = $tag->getTag("id");
if($idTag instanceof ShortTag){
$item = ItemFactory::get(Binary::unsignShort($idTag->getValue()), $meta, $count);
$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

@ -246,6 +246,8 @@ class ItemFactory{
self::registerItem(new Item(Item::NAUTILUS_SHELL, 0, "Nautilus Shell"));
self::registerItem(new GoldenAppleEnchanted());
self::registerItem(new Item(Item::HEART_OF_THE_SEA, 0, "Heart of the Sea"));
self::registerItem(new Item(Item::TURTLE_SHELL_PIECE, 0, "Scute"));
//TODO: TURTLE_HELMET
//TODO: COMPOUND
//TODO: RECORD_13
@ -281,7 +283,7 @@ class ItemFactory{
throw new \RuntimeException("Trying to overwrite an already registered item");
}
self::$list[$id] = clone $item;
self::$list[self::getListOffset($id)] = clone $item;
}
/**
@ -302,10 +304,10 @@ class ItemFactory{
try{
/** @var Item|null $listed */
$listed = self::$list[$id];
$listed = self::$list[self::getListOffset($id)];
if($listed !== null){
$item = clone $listed;
}elseif($id < 256){
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
* crafting ingredients with any-damage. */
$item = new ItemBlock($id, $meta);
@ -353,13 +355,13 @@ class ItemFactory{
if(!isset($b[1])){
$meta = 0;
}elseif(is_numeric($b[1])){
$meta = $b[1] & 0xFFFF;
$meta = (int) $b[1];
}else{
throw new \InvalidArgumentException("Unable to parse \"" . $b[1] . "\" from \"" . $str . "\" as a valid meta value");
}
if(is_numeric($b[0])){
$item = self::get(((int) $b[0]) & 0xFFFF, $meta);
$item = self::get((int) $b[0], $meta);
}elseif(defined(ItemIds::class . "::" . strtoupper($b[0]))){
$item = self::get(constant(ItemIds::class . "::" . strtoupper($b[0])), $meta);
}else{
@ -380,6 +382,13 @@ class ItemFactory{
if($id < 256){
return BlockFactory::isRegistered($id);
}
return self::$list[$id] !== null;
return self::$list[self::getListOffset($id)] !== null;
}
private static function getListOffset(int $id) : int{
if($id < -0x8000 or $id > 0x7fff){
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
}
return $id & 0xffff;
}
}

View File

@ -235,6 +235,8 @@ interface ItemIds extends BlockIds{
public const NAUTILUS_SHELL = 465;
public const APPLEENCHANTED = 466, APPLE_ENCHANTED = 466, ENCHANTED_GOLDEN_APPLE = 466;
public const HEART_OF_THE_SEA = 467;
public const TURTLE_SHELL_PIECE = 468;
public const TURTLE_HELMET = 469;
public const COMPOUND = 499;
public const RECORD_13 = 500;

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{

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

@ -114,13 +114,26 @@ class Enchantment{
self::registerEnchantment(new ProtectionEnchantment(self::PROJECTILE_PROTECTION, "%enchantment.protect.projectile", self::RARITY_UNCOMMON, self::SLOT_ARMOR, self::SLOT_NONE, 4, 1.5, [
EntityDamageEvent::CAUSE_PROJECTILE
]));
self::registerEnchantment(new Enchantment(self::THORNS, "%enchantment.thorns", self::RARITY_MYTHIC, self::SLOT_TORSO, self::SLOT_HEAD | self::SLOT_LEGS | self::SLOT_FEET, 3));
self::registerEnchantment(new Enchantment(self::RESPIRATION, "%enchantment.oxygen", self::RARITY_RARE, self::SLOT_HEAD, self::SLOT_NONE, 3));
self::registerEnchantment(new SharpnessEnchantment(self::SHARPNESS, "%enchantment.damage.all", self::RARITY_COMMON, self::SLOT_SWORD, self::SLOT_AXE, 5));
//TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet)
self::registerEnchantment(new KnockbackEnchantment(self::KNOCKBACK, "%enchantment.knockback", self::RARITY_UNCOMMON, self::SLOT_SWORD, self::SLOT_NONE, 2));
self::registerEnchantment(new FireAspectEnchantment(self::FIRE_ASPECT, "%enchantment.fire", self::RARITY_RARE, self::SLOT_SWORD, self::SLOT_NONE, 2));
self::registerEnchantment(new Enchantment(self::EFFICIENCY, "%enchantment.digging", self::RARITY_COMMON, self::SLOT_DIG, self::SLOT_SHEARS, 5));
self::registerEnchantment(new Enchantment(self::SILK_TOUCH, "%enchantment.untouching", self::RARITY_MYTHIC, self::SLOT_DIG, self::SLOT_SHEARS, 1));
self::registerEnchantment(new Enchantment(self::UNBREAKING, "%enchantment.durability", self::RARITY_UNCOMMON, self::SLOT_DIG | self::SLOT_ARMOR | self::SLOT_FISHING_ROD | self::SLOT_BOW, self::SLOT_TOOL | self::SLOT_CARROT_STICK | self::SLOT_ELYTRA, 3));
self::registerEnchantment(new Enchantment(self::POWER, "%enchantment.arrowDamage", self::RARITY_COMMON, self::SLOT_BOW, self::SLOT_NONE, 5));
self::registerEnchantment(new Enchantment(self::PUNCH, "%enchantment.arrowKnockback", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 2));
self::registerEnchantment(new Enchantment(self::FLAME, "%enchantment.arrowFire", self::RARITY_RARE, self::SLOT_BOW, self::SLOT_NONE, 1));
self::registerEnchantment(new Enchantment(self::INFINITY, "%enchantment.arrowInfinite", self::RARITY_MYTHIC, self::SLOT_BOW, self::SLOT_NONE, 1));
self::registerEnchantment(new Enchantment(self::MENDING, "%enchantment.mending", self::RARITY_RARE, self::SLOT_NONE, self::SLOT_ALL, 1));
self::registerEnchantment(new Enchantment(self::VANISHING, "%enchantment.curse.vanishing", self::RARITY_MYTHIC, self::SLOT_NONE, self::SLOT_ALL, 1));
}

View File

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

View File

@ -0,0 +1,41 @@
<?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\item\enchantment;
use pocketmine\entity\Entity;
class FireAspectEnchantment extends MeleeWeaponEnchantment{
public function isApplicableTo(Entity $victim) : bool{
return true;
}
public function getDamageBonus(int $enchantmentLevel) : float{
return 0;
}
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
$victim->setOnFire($enchantmentLevel * 4);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
class KnockbackEnchantment extends MeleeWeaponEnchantment{
public function isApplicableTo(Entity $victim) : bool{
return $victim instanceof Living;
}
public function getDamageBonus(int $enchantmentLevel) : float{
return 0;
}
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
if($victim instanceof Living){
$victim->knockBack($attacker, 0, $victim->x - $attacker->x, $victim->z - $attacker->z, $enchantmentLevel * 0.5);
}
}
}

View File

@ -0,0 +1,63 @@
<?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\item\enchantment;
use pocketmine\entity\Entity;
/**
* Classes extending this class can be applied to weapons and activate when used by a mob to attack another mob in melee
* combat.
*/
abstract class MeleeWeaponEnchantment extends Enchantment{
/**
* Returns whether this melee enchantment has an effect on the target entity. For example, Smite only applies to
* undead mobs.
*
* @param Entity $victim
*
* @return bool
*/
abstract public function isApplicableTo(Entity $victim) : bool;
/**
* Returns the amount of additional damage caused by this enchantment to applicable targets.
*
* @param int $enchantmentLevel
*
* @return float
*/
abstract public function getDamageBonus(int $enchantmentLevel) : float;
/**
* Called after damaging the entity to apply any post damage effects to the target.
*
* @param Entity $attacker
* @param Entity $victim
* @param int $enchantmentLevel
*/
public function onPostAttack(Entity $attacker, Entity $victim, int $enchantmentLevel) : void{
}
}

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

@ -0,0 +1,37 @@
<?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\item\enchantment;
use pocketmine\entity\Entity;
class SharpnessEnchantment extends MeleeWeaponEnchantment{
public function isApplicableTo(Entity $victim) : bool{
return true;
}
public function getDamageBonus(int $enchantmentLevel) : float{
return 0.5 * ($enchantmentLevel + 1);
}
}

View File

@ -200,6 +200,8 @@ class Level implements ChunkManager, Metadatable{
private $chunkPopulationLock = [];
/** @var int */
private $chunkPopulationQueueSize = 2;
/** @var bool[] */
private $generatorRegisteredWorkers = [];
/** @var bool */
private $autoSave = true;
@ -245,9 +247,6 @@ class Level implements ChunkManager, Metadatable{
/** @var bool */
private $closed = false;
/** @var \Closure */
private $asyncPoolStartHook;
public static function chunkHash(int $x, int $z) : int{
return (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF);
}
@ -285,6 +284,7 @@ class Level implements ChunkManager, Metadatable{
/**
* @param string $str
*
* @return int
*/
public static function getDifficultyFromString(string $str) : int{
@ -363,10 +363,6 @@ class Level implements ChunkManager, Metadatable{
$this->temporalPosition = new Position(0, 0, 0, $this);
$this->temporalVector = new Vector3(0, 0, 0);
$this->tickRate = 1;
$this->server->getAsyncPool()->addWorkerStartHook($this->asyncPoolStartHook = function(int $worker) : void{
$this->registerGeneratorToWorker($worker);
});
}
public function getTickRate() : int{
@ -382,15 +378,18 @@ class Level implements ChunkManager, Metadatable{
}
public function registerGeneratorToWorker(int $worker) : void{
$this->generatorRegisteredWorkers[$worker] = true;
$this->server->getAsyncPool()->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getGeneratorOptions()), $worker);
}
public function unregisterGenerator(){
$pool = $this->server->getAsyncPool();
$pool->removeWorkerStartHook($this->asyncPoolStartHook);
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
if(isset($this->generatorRegisteredWorkers[$i])){
$pool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
}
}
$this->generatorRegisteredWorkers = [];
}
public function getBlockMetadata() : BlockMetadataStore{
@ -848,18 +847,18 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @param Player[] $target
* @param Block[] $blocks
* @param int $flags
* @param bool $optimizeRebuilds
* @param Player[] $target
* @param Vector3[] $blocks
* @param int $flags
* @param bool $optimizeRebuilds
*/
public function sendBlocks(array $target, array $blocks, int $flags = UpdateBlockPacket::FLAG_NONE, bool $optimizeRebuilds = false){
$packets = [];
if($optimizeRebuilds){
$chunks = [];
foreach($blocks as $b){
if($b === null){
continue;
if(!($b instanceof Vector3)){
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
}
$pk = new UpdateBlockPacket();
@ -874,24 +873,20 @@ 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;
}
}else{
foreach($blocks as $b){
if($b === null){
continue;
if(!($b instanceof Vector3)){
throw new \TypeError("Expected Vector3 in blocks array, got " . (is_object($b) ? get_class($b) : gettype($b)));
}
$pk = new UpdateBlockPacket();
@ -900,16 +895,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;
@ -981,13 +972,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();
}
@ -1016,6 +1014,8 @@ class Level implements ChunkManager, Metadatable{
}
}
}
skip_to_next: //dummy label to break out of nested loops
}
if($this->clearChunksOnTick){
@ -1778,7 +1778,7 @@ 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
}
@ -1866,7 +1866,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();
@ -2953,8 +2953,13 @@ class Level implements ChunkManager, Metadatable{
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
}
$task = new PopulationTask($this, $chunk);
$this->server->getAsyncPool()->submitTask($task);
$workerId = $this->server->getAsyncPool()->selectWorker();
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
}
Timings::$populationTimer->stopTiming();
@ -2974,6 +2979,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;
@ -75,8 +75,8 @@ class Chunk{
/** @var Entity[] */
protected $entities = [];
/** @var int[] */
protected $heightMap = [];
/** @var \SplFixedArray|int[] */
protected $heightMap;
/** @var string */
protected $biomeIds;
@ -110,11 +110,11 @@ class Chunk{
}
if(count($heightMap) === 256){
$this->heightMap = $heightMap;
$this->heightMap = \SplFixedArray::fromArray($heightMap);
}else{
assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap));
$val = ($this->height * 16);
$this->heightMap = array_fill(0, 256, $val);
$this->heightMap = \SplFixedArray::fromArray(array_fill(0, 256, $val));
}
if(strlen($biomeIds) === 256){
@ -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
*
@ -739,7 +745,7 @@ class Chunk{
* @return int[]
*/
public function getHeightMapArray() : array{
return $this->heightMap;
return $this->heightMap->toArray();
}
/**
@ -830,18 +836,13 @@ 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
if(!($subChunk instanceof EmptySubChunk) and $subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
$this->subChunks[$y] = $this->emptySubChunk;
}else{
continue; //do not set changed
}
$this->hasChanged = true;
}
}
@ -858,8 +859,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 +882,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 +922,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

@ -106,13 +106,14 @@ class SubChunk implements SubChunkInterface{
if($data !== null){
$i >>= 1;
$byte = ord($this->data{$i});
$oldPair = ord($this->data{$i});
if(($y & 1) === 0){
$this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f));
$newPair = ($oldPair & 0xf0) | ($data & 0x0f);
}else{
$this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f));
$newPair = (($data & 0x0f) << 4) | ($oldPair & 0x0f);
}
if($this->data{$i} !== $byte){
if($newPair !== $oldPair){
$this->data{$i} = chr($newPair);
$changed = true;
}
}
@ -222,23 +223,6 @@ 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 [];
}

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,36 +38,22 @@ 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);

View File

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

View File

@ -27,7 +27,6 @@ use pocketmine\level\format\io\leveldb\LevelDB;
use pocketmine\level\format\io\region\Anvil;
use pocketmine\level\format\io\region\McRegion;
use pocketmine\level\format\io\region\PMAnvil;
use pocketmine\level\LevelException;
abstract class LevelProviderManager{
protected static $providers = [];
@ -42,12 +41,21 @@ abstract class LevelProviderManager{
/**
* @param string $class
*
* @throws LevelException
* @throws \InvalidArgumentException
*/
public static function addProvider(string $class){
if(!is_subclass_of($class, LevelProvider::class)){
throw new LevelException("Class is not a subclass of LevelProvider");
try{
$reflection = new \ReflectionClass($class);
}catch(\ReflectionException $e){
throw new \InvalidArgumentException("Class $class does not exist");
}
if(!$reflection->implementsInterface(LevelProvider::class)){
throw new \InvalidArgumentException("Class $class does not implement " . LevelProvider::class);
}
if(!$reflection->isInstantiable()){
throw new \InvalidArgumentException("Class $class cannot be constructed");
}
/** @var LevelProvider $class */
self::$providers[strtolower($class::getProviderName())] = $class;
}

View File

@ -35,7 +35,6 @@ class RegionLoader{
public const MAX_SECTOR_LENGTH = 256 << 12; //256 sectors, (1 MiB)
public const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
public const MAX_REGION_FILE_SIZE = 32 * 32 * self::MAX_SECTOR_LENGTH + self::REGION_HEADER_LENGTH; //32 * 32 1MiB chunks + header size
public static $COMPRESSION_LEVEL = 7;
@ -64,13 +63,8 @@ class RegionLoader{
$exists = file_exists($this->filePath);
if(!$exists){
touch($this->filePath);
}else{
$fileSize = filesize($this->filePath);
if($fileSize > self::MAX_REGION_FILE_SIZE){
throw new CorruptedRegionException("Corrupted oversized region file found, should be a maximum of " . self::MAX_REGION_FILE_SIZE . " bytes, got " . $fileSize . " bytes");
}elseif($fileSize % 4096 !== 0){
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
}
}elseif(filesize($this->filePath) % 4096 !== 0){
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
}
$this->filePointer = fopen($this->filePath, "r+b");

View File

@ -26,11 +26,13 @@ namespace pocketmine\level\generator;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\item\ItemFactory;
use pocketmine\level\ChunkManager;
use pocketmine\level\format\Chunk;
use pocketmine\level\generator\object\OreType;
use pocketmine\level\generator\populator\Ore;
use pocketmine\level\generator\populator\Populator;
use pocketmine\math\Vector3;
use pocketmine\utils\Random;
class Flat extends Generator{
/** @var Chunk */
@ -41,6 +43,8 @@ class Flat extends Generator{
private $structure;
/** @var int */
private $floorLevel;
/** @var int */
private $biome;
/** @var mixed[] */
private $options;
/** @var string */
@ -55,9 +59,15 @@ class Flat extends Generator{
}
public function __construct(array $options = []){
$this->preset = "2;7,2x3,2;1;";
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
$this->options = $options;
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
$this->preset = $this->options["preset"];
}else{
$this->preset = "2;7,2x3,2;1;";
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
}
$this->parsePreset();
if(isset($this->options["decoration"])){
$ores = new Ore();
@ -90,39 +100,14 @@ class Flat extends Generator{
return $result;
}
protected function generateBaseChunk(string $preset) : void{
$this->preset = $preset;
$preset = explode(";", $preset);
$version = (int) $preset[0];
protected function parsePreset() : void{
$preset = explode(";", $this->preset);
$blocks = (string) ($preset[1] ?? "");
$biome = (int) ($preset[2] ?? 1);
$this->biome = (int) ($preset[2] ?? 1);
$options = (string) ($preset[3] ?? "");
$this->structure = self::parseLayers($blocks);
$this->floorLevel = $y = count($this->structure);
$this->chunk = new Chunk(0, 0);
$this->chunk->setGenerated();
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$this->chunk->setBiomeId($X, $Z, $biome);
}
}
$count = count($this->structure);
for($sy = 0; $sy < $count; $sy += 16){
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
list($id, $meta) = $this->structure[$y | $sy];
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$subchunk->setBlock($X, $y, $Z, $id, $meta);
}
}
}
}
$this->floorLevel = count($this->structure);
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
foreach($matches[2] as $i => $option){
@ -141,14 +126,37 @@ class Flat extends Generator{
}
}
public function generateChunk(int $chunkX, int $chunkZ) : void{
if($this->chunk === null){
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
$this->generateBaseChunk($this->options["preset"]);
}else{
$this->generateBaseChunk($this->preset);
protected function generateBaseChunk() : void{
$this->chunk = new Chunk(0, 0);
$this->chunk->setGenerated();
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$this->chunk->setBiomeId($X, $Z, $this->biome);
}
}
$count = count($this->structure);
for($sy = 0; $sy < $count; $sy += 16){
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
list($id, $meta) = $this->structure[$y | $sy];
for($Z = 0; $Z < 16; ++$Z){
for($X = 0; $X < 16; ++$X){
$subchunk->setBlock($X, $y, $Z, $id, $meta);
}
}
}
}
}
public function init(ChunkManager $level, Random $random) : void{
parent::init($level, $random);
$this->generateBaseChunk();
}
public function generateChunk(int $chunkX, int $chunkZ) : void{
$chunk = clone $this->chunk;
$chunk->setX($chunkX);
$chunk->setZ($chunkZ);

View File

@ -33,9 +33,9 @@ abstract class Populator{
/**
* @param ChunkManager $level
* @param int $chunkX
* @param int $chunkZ
* @param Random $random
* @param int $chunkX
* @param int $chunkZ
* @param Random $random
*
* @return mixed
*/

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\level\light;
use pocketmine\block\BlockFactory;
use pocketmine\level\format\Chunk;
use pocketmine\level\Level;
use pocketmine\scheduler\AsyncTask;
@ -39,6 +40,9 @@ class LightPopulationTask extends AsyncTask{
}
public function onRun(){
if(!BlockFactory::isInit()){
BlockFactory::init();
}
/** @var Chunk $chunk */
$chunk = Chunk::fastDeserialize($this->chunk);

View File

@ -106,6 +106,8 @@ abstract class LightUpdate{
while(!$this->spreadQueue->isEmpty()){
list($x, $y, $z) = $this->spreadQueue->dequeue();
unset($this->spreadVisited[Level::blockHash($x, $y, $z)]);
if(!$this->subChunkHandler->moveTo($x, $y, $z)){
continue;
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\level\particle;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
@ -35,7 +34,7 @@ class DestroyBlockParticle extends Particle{
public function __construct(Vector3 $pos, Block $b){
parent::__construct($pos->x, $pos->y, $pos->z);
$this->data = BlockFactory::toStaticRuntimeId($b->getId(), $b->getDamage());
$this->data = $b->getRuntimeId();
}
public function encode(){

View File

@ -95,7 +95,7 @@ class FloatingTextParticle extends Particle{
$add = new PlayerListPacket();
$add->type = PlayerListPacket::TYPE_ADD;
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, $name, 0, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
$add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, new Skin("Standard_Custom", str_repeat("\x00", 8192)))];
$p[] = $add;
$pk = new AddPlayerPacket();
@ -109,8 +109,8 @@ class FloatingTextParticle extends Particle{
1 << Entity::DATA_FLAG_IMMOBILE
);
$pk->metadata = [
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
Entity::DATA_SCALE => [Entity::DATA_TYPE_FLOAT, 0.01] //zero causes problems on debug builds
Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags],
Entity::DATA_SCALE => [Entity::DATA_TYPE_FLOAT, 0.01] //zero causes problems on debug builds
];
$p[] = $pk;

View File

@ -24,11 +24,10 @@ declare(strict_types=1);
namespace pocketmine\level\particle;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\math\Vector3;
class TerrainParticle extends GenericParticle{
public function __construct(Vector3 $pos, Block $b){
parent::__construct($pos, Particle::TYPE_TERRAIN, BlockFactory::toStaticRuntimeId($b->getId(), $b->getDamage()));
parent::__construct($pos, Particle::TYPE_TERRAIN, $b->getRuntimeId());
}
}

View File

@ -109,7 +109,12 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarInt($auxValue);
$nbt = $item->getCompoundTag();
$this->putLShort(strlen($nbt));
$nbtLen = strlen($nbt);
if($nbtLen > 32767){
throw new \InvalidArgumentException("NBT encoded length must be < 32768, got $nbtLen bytes");
}
$this->putLShort($nbtLen);
$this->put($nbt);
$this->putVarInt(0); //CanPlaceOn entry count (TODO)

View File

@ -79,6 +79,7 @@ use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveEntityDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
@ -100,6 +101,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\RiderJumpPacket;
use pocketmine\network\mcpe\protocol\ScriptCustomEventPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\ServerSettingsResponsePacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
@ -115,6 +117,7 @@ use pocketmine\network\mcpe\protocol\SetLastHurtByPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\SetScorePacket;
use pocketmine\network\mcpe\protocol\SetScoreboardIdentityPacket;
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
@ -134,6 +137,7 @@ use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdateSoftEnumPacket;
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
use pocketmine\network\mcpe\protocol\WSConnectPacket;
@ -585,7 +589,23 @@ abstract class NetworkSession{
return false;
}
public function handleSetScoreboardIdentity(SetScoreboardIdentityPacket $packet) : bool{
return false;
}
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
return false;
}
public function handleUpdateSoftEnum(UpdateSoftEnumPacket $packet) : bool{
return false;
}
public function handleNetworkStackLatency(NetworkStackLatencyPacket $packet) : bool{
return false;
}
public function handleScriptCustomEvent(ScriptCustomEventPacket $packet) : bool{
return false;
}
}

View File

@ -79,6 +79,10 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
}
public function handleDataPacket(DataPacket $packet){
if(!$this->player->isConnected()){
return;
}
$timings = Timings::getReceiveDataPacketTimings($packet);
$timings->startTiming();
@ -239,7 +243,37 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
}
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
return false; //TODO: GUI stuff
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
}
/**
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
*
* @param string $json
* @param bool $assoc
*
* @return mixed
*/
private static function stupid_json_decode(string $json, bool $assoc = false){
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
$parts = preg_split('/(?:"(?:\\"|[^"])*"|)\K(,)/', $matches[1]); //Splits on commas not inside quotes, ignoring escaped quotes
foreach($parts as $k => $part){
$part = trim($part);
if($part === ""){
$part = "\"\"";
}
$parts[$k] = $part;
}
$fixed = "[" . implode(",", $parts) . "]";
if(($ret = json_decode($fixed, $assoc)) === null){
throw new \InvalidArgumentException("Failed to fix JSON: " . json_last_error_msg() . "(original: $json, modified: $fixed)");
}
return $ret;
}
return json_decode($json, $assoc);
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{

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