Compare commits

...

252 Commits
3.1.4 ... 3.3.0

Author SHA1 Message Date
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
6e6cda91ce Release 3.1.7 2018-09-11 11:45:17 +01:00
53a76c0d14 Merge branch 'release/3.1' into release/3.2 2018-09-11 11:35:38 +01:00
69500fe183 LightUpdate: Remove garbage left over from dab73d8950 2018-09-11 11:35:31 +01:00
5af4dd20df Merge branch 'release/3.1' into release/3.2 2018-09-08 14:25:11 +01:00
c7d58db7eb Cleanup Entity age handling, fixed arrows despawning too quickly after long flight 2018-09-08 14:23:06 +01:00
a3b78236eb Server: don't catch Throwable for level ticking
this usually causes the console to get spammed with errors. Additionally, in the case where doTick() throws any exception, it's usually because we're in a state we didn't want to be in, so we really should not carry on trying to keep ticking when something breaks here. Instead, this should generate a crashdump.
2018-09-08 14:13:28 +01:00
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
c447d51e3f Bucket: use ItemFactory instead of self-clone
in the future Item->setDamage() will be removed.
2018-09-06 18:42:09 +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
b615cad22d 3.1.7 is next 2018-09-04 11:50:05 +01:00
b93e219231 Release 3.1.6 2018-09-04 11:44:05 +01:00
a4a9309193 Updated NBT dependency for bug fix 2018-09-04 11:43:38 +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
3a85e6cab9 Backport ce58294305 for 3.x line 2018-08-30 15:46:26 +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
17f1bf5512 3.1.6 is next 2018-08-29 16:08:45 +01:00
5179bb1d30 Release 3.1.5 2018-08-29 15:57:58 +01:00
6bff840293 Sync 3.1 and 3.0 branches 2018-08-29 15:55:14 +01:00
08897c6941 Release 3.0.12 2018-08-29 15:47:28 +01:00
05d9bb45d0 Merge branch 'release/3.1' into release/3.2 2018-08-26 19:15:16 +01:00
dfe2aa9c67 Merge branch 'release/3.0' into release/3.1 2018-08-26 19:15:08 +01:00
4006be35d9 Update BinaryUtils dependency 2018-08-26 19:15:01 +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
c7af1cf785 Merge branch 'release/3.0' into release/3.1 2018-08-25 17:49:14 +01:00
22fcfffa53 PluginsCommand: remove useless private function 2018-08-25 16:09:51 +01:00
7dd53f2397 Replace unnecessary strlen > 0 calls with !== "" checks 2018-08-25 16:07:49 +01:00
298259b473 PluginsCommand: clean up garbage code 2018-08-25 16:05:25 +01:00
c123f2d10b SimpleCommandMap: clean up error reporting in registerServerAliases() 2018-08-25 15:43:23 +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
4cc7573a64 Merge branch 'release/3.0' into release/3.1 2018-08-22 15:40:43 +01:00
9d80802e53 Living: fixed some update conditions not being set 2018-08-22 15:40:32 +01:00
ec1e257e21 Entity: Fixed fire damage not taking effect in the void
this is an absurd bug that nobody would ever otherwise notice, but the problem is that the doOnFireTick() call isn't evaluated if hasUpdate is already true.
2018-08-22 15:36:31 +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
6fce2b3349 Merge branch 'release/3.0' into release/3.1 2018-08-19 19:52:30 +01:00
64ed8adefc Cactus: fixed place/break logic mismatch causing item spewing 2018-08-19 19:52:07 +01:00
2eda8cfad3 Merge branch 'release/3.1' into release/3.2 2018-08-19 16:01:47 +01:00
91be5aba0c Merge branch 'release/3.0' into release/3.1 2018-08-19 16:00:25 +01:00
5df601c817 Add @see docs so PhpStorm can see dynamic constructor usages
PhpStorm can't see constructor usages when the class name is dynamic. This causes maintenance problems because cross-referencing constructors called like this doesn't show up dynamic calls.
2018-08-19 16:00:15 +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
1a47735d84 Merge branch 'release/3.0' into release/3.1 2018-08-19 13:59:34 +01:00
0cdf4d0c55 Spoon the TesterPlugin into the main repository
I am eating my own words this once, because having the tester plugin as a separate repository makes no sense - it is just added barriers to writing proper tests with no actual benefit. Since the tester plugin is specifically intended for CI, it doesn't make sense for it to be in its own module.
2018-08-19 13:58:13 +01:00
e6e28b74b5 Nuke the TesterPlugin submodule 2018-08-19 13:47:43 +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
72f46b4631 Merge branch 'release/3.0' into release/3.1 2018-08-19 11:22:51 +01:00
3892f2f404 Config: Properly prevent keys getting transformed into bools
The original regex almost completely failed at its objective, because it a) only worked if there was no value for the key, and b) did not prevent all such occurrences getting transformed, while quoting patterns that would not get transformed anyway.
2018-08-19 11:22:36 +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
4a1d67cb91 Merge branch 'release/3.0' into release/3.1 2018-08-17 19:47:34 +01:00
b4694092b7 Painting: fix motive not being saved 2018-08-17 19:47:26 +01:00
4b3e17e681 Sync 3.2 and 3.1 branches 2018-08-16 18:33:54 +01:00
d99ee515c6 3.1.5 is next 2018-08-16 18:33:27 +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
144 changed files with 3309 additions and 1081 deletions

3
.gitmodules vendored
View File

@ -7,9 +7,6 @@
[submodule "tests/plugins/PocketMine-DevTools"]
path = tests/plugins/PocketMine-DevTools
url = https://github.com/pmmp/PocketMine-DevTools.git
[submodule "tests/plugins/PocketMine-TesterPlugin"]
path = tests/plugins/PocketMine-TesterPlugin
url = https://github.com/pmmp/PocketMine-TesterPlugin.git
[submodule "src/pocketmine/resources/vanilla"]
path = src/pocketmine/resources/vanilla
url = https://github.com/pmmp/BedrockData.git

View File

@ -27,7 +27,7 @@
"pocketmine/raklib": "^0.12.0",
"pocketmine/spl": "^0.3.0",
"pocketmine/binaryutils": "^0.1.0",
"pocketmine/nbt": "^0.2.0",
"pocketmine/nbt": "^0.2.1",
"pocketmine/math": "^0.2.0",
"pocketmine/snooze": "^0.1.0"
},

46
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2670b9e2a730ff758909be8b9e9d609a",
"content-hash": "3536995c56bfc3dbd6ccc0994e88a636",
"packages": [
{
"name": "pocketmine/binaryutils",
"version": "0.1.0",
"version": "0.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595"
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595",
"reference": "c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/54efeb978be0ff9335022729fe63c1e2077bf1be",
"reference": "54efeb978be0ff9335022729fe63c1e2077bf1be",
"shasum": ""
},
"require": {
@ -38,20 +38,20 @@
"source": "https://github.com/pmmp/BinaryUtils/tree/master",
"issues": "https://github.com/pmmp/BinaryUtils/issues"
},
"time": "2018-04-16T09:05:08+00:00"
"time": "2018-08-26T18:11:05+00:00"
},
{
"name": "pocketmine/math",
"version": "0.2.0",
"version": "0.2.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Math.git",
"reference": "95ae5600328ed2add44c0bc830a68d3660e9e0ef"
"reference": "ee299f5c9c444ca526c9c691b920f321458cf0b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Math/zipball/95ae5600328ed2add44c0bc830a68d3660e9e0ef",
"reference": "95ae5600328ed2add44c0bc830a68d3660e9e0ef",
"url": "https://api.github.com/repos/pmmp/Math/zipball/ee299f5c9c444ca526c9c691b920f321458cf0b6",
"reference": "ee299f5c9c444ca526c9c691b920f321458cf0b6",
"shasum": ""
},
"require": {
@ -69,23 +69,23 @@
],
"description": "PHP library containing math related code used in PocketMine-MP",
"support": {
"source": "https://github.com/pmmp/Math/tree/master",
"source": "https://github.com/pmmp/Math/tree/0.2.1",
"issues": "https://github.com/pmmp/Math/issues"
},
"time": "2018-06-09T09:26:30+00:00"
"time": "2018-08-15T15:43:27+00:00"
},
{
"name": "pocketmine/nbt",
"version": "0.2.0",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c"
"reference": "474f0cf0a47656d0122b4f3f71302e694ed6977b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
"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.0",
"source": "https://github.com/pmmp/NBT/tree/0.2.2",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2018-06-13T09:56:00+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);
}

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;
@ -193,9 +197,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*/
protected $sessionAdapter;
/** @var int */
protected $protocol = -1;
/** @var string */
protected $ip;
/** @var int */
@ -322,6 +323,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
*/
@ -637,8 +643,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 +654,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 +1025,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,
@ -1839,8 +1846,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 +1928,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 +2035,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);
@ -2499,6 +2503,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 +2543,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 +2721,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 +2785,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:
@ -3147,12 +3170,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
*
* @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()){
@ -3330,6 +3355,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 +3417,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();
@ -3500,123 +3567,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.4";
const BASE_VERSION = "3.3.0";
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;
@ -99,6 +101,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;
@ -1025,14 +1028,8 @@ class Server{
return false;
}
try{
$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;
@ -1075,20 +1072,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);
$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));
@ -1146,17 +1138,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;
@ -1303,7 +1290,7 @@ class Server{
if(($player = $this->getPlayerExact($name)) !== null){
$player->recalculatePermissions();
}
$this->operators->save(true);
$this->operators->save();
}
/**
@ -1323,7 +1310,7 @@ class Server{
*/
public function addWhitelist(string $name){
$this->whitelist->set(strtolower($name), true);
$this->whitelist->save(true);
$this->whitelist->save();
}
/**
@ -1636,7 +1623,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());
@ -1694,7 +1681,7 @@ class Server{
}
if($this->properties->hasChanged()){
$this->properties->save(true);
$this->properties->save();
}
if(!($this->getDefaultLevel() instanceof Level)){
@ -1745,7 +1732,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
}
@ -1771,7 +1758,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
}
@ -1801,7 +1788,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
}
@ -1826,7 +1813,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
}
@ -1951,10 +1938,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;
}
@ -1974,6 +1971,7 @@ class Server{
$this->pluginManager->disablePlugins();
$this->pluginManager->clearPlugins();
PermissionManager::getInstance()->clearPermissions();
$this->commandMap->clearCommands();
$this->logger->info("Reloading properties...");
@ -2216,7 +2214,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",
@ -2311,7 +2309,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);
}
@ -2334,7 +2332,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);
@ -2350,41 +2348,37 @@ class Server{
}
//Do level ticks
foreach($this->getLevels() as $level){
foreach($this->levels as $k => $level){
if(!isset($this->levels[$k])){
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
continue;
}
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
continue;
}
try{
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$levelTime = microtime(true);
$level->doTick($currentTick);
$tickMs = (microtime(true) - $levelTime) * 1000;
$level->tickRateTime = $tickMs;
if($this->autoTickRate){
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
$level->setTickRate($r = $level->getTickRate() - 1);
if($r > $this->baseTickRate){
$level->tickRateCounter = $level->getTickRate();
}
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
}elseif($tickMs >= 50){
if($level->getTickRate() === $this->baseTickRate){
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
$level->setTickRate($level->getTickRate() + 1);
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
}
$level->tickRateCounter = $level->getTickRate();
}
}catch(\Throwable $e){
if(!$level->isClosed()){
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickError", [$level->getName(), $e->getMessage()]));
}else{
$this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickUnloadError", [$level->getName()]));
}
$this->logger->logException($e);
}
}
}
@ -2531,13 +2525,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

@ -421,8 +421,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

@ -77,7 +77,7 @@ class Cactus extends Transparent{
}else{
for($side = 2; $side <= 5; ++$side){
$b = $this->getSide($side);
if(!$b->canBeFlowedInto()){
if($b->isSolid()){
$this->getLevel()->useBreakOn($this);
break;
}
@ -117,7 +117,7 @@ class Cactus extends Transparent{
$block1 = $this->getSide(Vector3::SIDE_SOUTH);
$block2 = $this->getSide(Vector3::SIDE_WEST);
$block3 = $this->getSide(Vector3::SIDE_EAST);
if($block0->isTransparent() and $block1->isTransparent() and $block2->isTransparent() and $block3->isTransparent()){
if(!$block0->isSolid() and !$block1->isSolid() and !$block2->isSolid() and !$block3->isSolid()){
$this->getLevel()->setBlock($this, $this, true);
return true;

View File

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

View File

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

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

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

@ -302,9 +302,9 @@ class SimpleCommandMap implements CommandMap{
}
$targets = [];
$bad = [];
$recursive = [];
$bad = "";
$recursive = "";
foreach($commandStrings as $commandString){
$args = explode(" ", $commandString);
$commandName = "";
@ -312,27 +312,21 @@ class SimpleCommandMap implements CommandMap{
if($command === null){
if(strlen($bad) > 0){
$bad .= ", ";
}
$bad .= $commandString;
$bad[] = $commandString;
}elseif($commandName === $alias){
if($recursive !== ""){
$recursive .= ", ";
}
$recursive .= $commandString;
$recursive[] = $commandString;
}else{
$targets[] = $commandString;
}
}
if($recursive !== ""){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, $recursive]));
if(!empty($recursive)){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.recursive", [$alias, implode(", ", $recursive)]));
continue;
}
if(strlen($bad) > 0){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad]));
if(!empty($bad)){
$this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, implode(", ", $bad)]));
continue;
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\lang\TranslationContainer;
use pocketmine\plugin\Plugin;
use pocketmine\utils\TextFormat;
class PluginsCommand extends VanillaCommand{
@ -43,20 +44,12 @@ class PluginsCommand extends VanillaCommand{
if(!$this->testPermission($sender)){
return true;
}
$this->sendPluginList($sender);
$list = array_map(function(Plugin $plugin) : string{
return ($plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED) . $plugin->getDescription()->getFullName();
}, $sender->getServer()->getPluginManager()->getPlugins());
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($list), implode(TextFormat::WHITE . ", ", $list)]));
return true;
}
private function sendPluginList(CommandSender $sender){
$list = "";
foreach(($plugins = $sender->getServer()->getPluginManager()->getPlugins()) as $plugin){
if(strlen($list) > 0){
$list .= TextFormat::WHITE . ", ";
}
$list .= $plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED;
$list .= $plugin->getDescription()->getFullName();
}
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($plugins), $list]));
}
}

View File

@ -49,7 +49,7 @@ class TeleportCommand extends VanillaCommand{
}
$args = array_values(array_filter($args, function($arg){
return strlen($arg) > 0;
return $arg !== "";
}));
if(count($args) < 1 or count($args) > 6){
throw new InvalidCommandSyntaxException();

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

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

@ -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[] */
@ -285,6 +289,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public static function createEntity($type, Level $level, CompoundTag $nbt, ...$args) : ?Entity{
if(isset(self::$knownEntities[$type])){
$class = self::$knownEntities[$type];
/** @see Entity::__construct() */
return new $class($level, $nbt, ...$args);
}
@ -407,8 +412,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public $boundingBox;
/** @var bool */
public $onGround;
/** @var int */
protected $age = 0;
/** @var float */
public $eyeHeight = null;
@ -609,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
*/
@ -734,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;
@ -774,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;
@ -1015,8 +1032,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$hasUpdate = true;
}
if($this->isOnFire()){
$hasUpdate = ($hasUpdate || $this->doOnFireTick($tickDiff));
if($this->isOnFire() and $this->doOnFireTick($tickDiff)){
$hasUpdate = true;
}
if($this->noDamageTicks > 0){
@ -1026,7 +1043,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
}
$this->age += $tickDiff;
$this->ticksLived += $tickDiff;
return $hasUpdate;

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);
}

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;
@ -476,6 +477,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 +511,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 +560,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,7 +654,9 @@ abstract class Living extends Entity implements Damageable{
$hasUpdate = parent::entityBaseTick($tickDiff);
$this->doEffectsTick($tickDiff);
if($this->doEffectsTick($tickDiff)){
$hasUpdate = true;
}
if($this->isAlive()){
if($this->isInsideOfSolid()){
@ -638,12 +665,8 @@ abstract class Living extends Entity implements Damageable{
$this->attack($ev);
}
if(!$this->canBreathe()){
$this->setBreathing(false);
$this->doAirSupplyTick($tickDiff);
}elseif(!$this->isBreathing()){
$this->setBreathing(true);
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
if($this->doAirSupplyTick($tickDiff)){
$hasUpdate = true;
}
}
@ -656,7 +679,7 @@ abstract class Living extends Entity implements Damageable{
return $hasUpdate;
}
protected function doEffectsTick(int $tickDiff = 1) : void{
protected function doEffectsTick(int $tickDiff = 1) : bool{
foreach($this->effects as $instance){
$type = $instance->getType();
if($type->canTick($instance)){
@ -667,25 +690,47 @@ abstract class Living extends Entity implements Damageable{
$this->removeEffect($instance->getId());
}
}
return !empty($this->effects);
}
/**
* 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;
}
/**
@ -693,7 +738,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();
}
/**

View File

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

@ -53,6 +53,9 @@ class ItemEntity extends Entity{
public $canCollide = false;
/** @var int */
protected $age = 0;
protected function initEntity() : void{
parent::initEntity();
@ -70,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));
@ -82,14 +88,13 @@ class ItemEntity extends Entity{
$hasUpdate = parent::entityBaseTick($tickDiff);
if(!$this->isFlaggedForDespawn()){
if($this->pickupDelay > 0 and $this->pickupDelay < 32767){ //Infinite delay
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
}
if(!$this->isFlaggedForDespawn() and $this->pickupDelay > -1 and $this->pickupDelay < 32767){ //Infinite delay
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
}
$this->age += $tickDiff;
if($this->age > 6000){
$this->server->getPluginManager()->callEvent($ev = new ItemDespawnEvent($this));
if($ev->isCancelled()){
@ -99,7 +104,6 @@ class ItemEntity extends Entity{
$hasUpdate = true;
}
}
}
return $hasUpdate;

View File

@ -84,6 +84,8 @@ class Painting extends Entity{
$this->namedtag->setByte("Facing", (int) $this->direction);
$this->namedtag->setByte("Direction", (int) $this->direction); //Save both for full compatibility
$this->namedtag->setString("Motive", $this->motive);
}
public function kill() : void{

View File

@ -40,19 +40,49 @@ 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;
public function __construct(Level $level, CompoundTag $nbt, ?Entity $shootingEntity = null, bool $critical = false){
parent::__construct($level, $nbt, $shootingEntity);
$this->setCritical($critical);
}
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);
}
public function isCritical() : bool{
return $this->getGenericFlag(self::DATA_FLAG_CRITICAL);
}
@ -70,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;
@ -77,9 +121,14 @@ class Arrow extends Projectile{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->age > 1200){
$this->flagForDespawn();
$hasUpdate = true;
if($this->isCollided){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();
$hasUpdate = true;
}
}else{
$this->collideTicks = 0;
}
return $hasUpdate;
@ -95,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;
@ -107,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

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

View File

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

View File

@ -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,7 +74,7 @@ abstract class Projectile extends Entity{
$this->setMaxHealth(1);
$this->setHealth(1);
$this->age = $this->namedtag->getShort("Age", $this->age);
$this->damage = $this->namedtag->getDouble("damage", $this->damage);
do{
$blockHit = null;
@ -112,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
@ -123,7 +143,7 @@ abstract class Projectile extends Entity{
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setShort("Age", $this->age);
$this->namedtag->setDouble("damage", $this->damage);
if($this->blockHit !== null){
$this->namedtag->setInt("tileX", $this->blockHit->x);
@ -140,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

@ -124,8 +124,6 @@ class SplashPotion extends Throwable{
}
}
}
$this->flagForDespawn();
}
/**

View File

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

View File

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

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

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

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

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

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

View File

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

View File

@ -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;
}
@ -916,7 +919,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 +954,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

@ -72,8 +72,6 @@ class Potion extends Item implements Consumable{
*
* @param int $id
* @return EffectInstance[]
*
* @throws \InvalidArgumentException if the potion type is unknown
*/
public static function getPotionEffectsById(int $id) : array{
switch($id){
@ -213,7 +211,7 @@ class Potion extends Item implements Consumable{
];
}
throw new \InvalidArgumentException("Unknown potion type $id");
return [];
}
public function __construct(int $meta = 0){

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

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

@ -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);
}
@ -363,10 +362,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 +377,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 +846,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 +872,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 +894,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;
@ -1778,7 +1768,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 +1856,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 +2943,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();

View File

@ -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){
@ -739,7 +739,7 @@ class Chunk{
* @return int[]
*/
public function getHeightMapArray() : array{
return $this->heightMap;
return $this->heightMap->toArray();
}
/**

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

@ -377,7 +377,7 @@ class LevelDB extends BaseLevelProvider{
/** @var CompoundTag[] $entities */
$entities = [];
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and $entityData !== ""){
$entities = $nbt->read($entityData, true);
if(!is_array($entities)){
$entities = [$entities];
@ -392,7 +392,7 @@ class LevelDB extends BaseLevelProvider{
}
$tiles = [];
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and strlen($tileData) > 0){
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and $tileData !== ""){
$tiles = $nbt->read($tileData, true);
if(!is_array($tiles)){
$tiles = [$tiles];
@ -402,7 +402,7 @@ class LevelDB extends BaseLevelProvider{
//TODO: extra data should be converted into blockstorage layers (first they need to be implemented!)
/*
$extraData = [];
if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and strlen($extraRawData) > 0){
if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and $extraRawData !== ""){
$binaryStream->setBuffer($extraRawData, 0);
$count = $binaryStream->getLInt();
for($i = 0; $i < $count; ++$i){

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

@ -52,7 +52,10 @@ class GeneratorRegisterTask extends AsyncTask{
$manager = new SimpleChunkManager($this->seed, $this->worldHeight);
$this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager);
/** @var Generator $generator */
/**
* @var Generator $generator
* @see Generator::__construct()
*/
$generator = new $this->generatorClass(unserialize($this->settings));
$generator->init($manager, new Random($manager->getSeed()));
$this->saveToThreadStore("generation.level{$this->levelId}.generator", $generator);

View File

@ -54,14 +54,6 @@ abstract class LightUpdate{
$this->subChunkHandler = new SubChunkIteratorManager($this->level);
}
public function addSpreadNode(int $x, int $y, int $z){
$this->spreadQueue->enqueue([$x, $y, $z]);
}
public function addRemoveNode(int $x, int $y, int $z, int $oldLight){
$this->spreadQueue->enqueue([$x, $y, $z, $oldLight]);
}
abstract protected function getLight(int $x, int $y, int $z) : int;
abstract protected function setLight(int $x, int $y, int $z, int $level);

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();

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

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

View File

@ -46,7 +46,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
* Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't
* communicate. It's important that we check this to avoid catastrophes.
*/
private const MCPE_RAKNET_PROTOCOL_VERSION = 8;
private const MCPE_RAKNET_PROTOCOL_VERSION = 9;
/** @var Server */
private $server;
@ -76,10 +76,6 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
$this->server = $server;
$this->sleeper = new SleeperNotifier();
$server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
$this->server->getNetwork()->processInterface($this);
});
$this->rakLib = new RakLibServer(
$this->server->getLogger(),
\pocketmine\COMPOSER_AUTOLOADER_PATH,
@ -92,6 +88,9 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
}
public function start(){
$this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
$this->server->getNetwork()->processInterface($this);
});
$this->rakLib->start(PTHREADS_INHERIT_CONSTANTS | PTHREADS_INHERIT_INI); //HACK: MainLogger needs INI and constants
}
@ -141,6 +140,10 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
$this->server->getPluginManager()->callEvent($ev);
$class = $ev->getPlayerClass();
/**
* @var Player $player
* @see Player::__construct()
*/
$player = new $class($this, $ev->getAddress(), $ev->getPort());
$this->players[$identifier] = $player;
$this->identifiersACK[$identifier] = 0;

View File

@ -145,7 +145,7 @@ class VerifyLoginTask extends AsyncTask{
public function onCompletion(Server $server){
/** @var Player $player */
$player = $this->fetchLocal();
if($player->isClosed()){
if(!$player->isConnected()){
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
}else{
$player->onVerifyCompleted($this->packet, $this->error, $this->authenticated);

View File

@ -38,10 +38,6 @@ class AddPlayerPacket extends DataPacket{
public $uuid;
/** @var string */
public $username;
/** @var string */
public $thirdPartyName = "";
/** @var int */
public $platform = 0;
/** @var int|null */
public $entityUniqueId = null; //TODO
/** @var int */
@ -75,11 +71,12 @@ class AddPlayerPacket extends DataPacket{
/** @var EntityLink[] */
public $links = [];
/** @var string */
public $deviceId = ""; //TODO: fill player's device ID (???)
protected function decodePayload(){
$this->uuid = $this->getUUID();
$this->username = $this->getString();
$this->thirdPartyName = $this->getString();
$this->platform = $this->getVarInt();
$this->entityUniqueId = $this->getEntityUniqueId();
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->platformChatId = $this->getString();
@ -103,13 +100,13 @@ class AddPlayerPacket extends DataPacket{
for($i = 0; $i < $linkCount; ++$i){
$this->links[$i] = $this->getEntityLink();
}
$this->deviceId = $this->getString();
}
protected function encodePayload(){
$this->putUUID($this->uuid);
$this->putString($this->username);
$this->putString($this->thirdPartyName);
$this->putVarInt($this->platform);
$this->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putString($this->platformChatId);
@ -133,6 +130,8 @@ class AddPlayerPacket extends DataPacket{
foreach($this->links as $link){
$this->putEntityLink($link);
}
$this->putString($this->deviceId);
}
public function handle(NetworkSession $session) : bool{

View File

@ -103,6 +103,13 @@ class AvailableCommandsPacket extends DataPacket{
*/
public $commandData = [];
/**
* @var CommandEnum[]
* List of dynamic command enums, also referred to as "soft" enums. These can by dynamically updated mid-game
* without resending this packet.
*/
public $softEnums = [];
protected function decodePayload(){
for($i = 0, $this->enumValuesCount = $this->getUnsignedVarInt(); $i < $this->enumValuesCount; ++$i){
$this->enumValues[] = $this->getString();
@ -119,6 +126,10 @@ class AvailableCommandsPacket extends DataPacket{
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->commandData[] = $this->getCommandData();
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->softEnums[] = $this->getSoftEnum();
}
}
protected function getEnum() : CommandEnum{
@ -133,6 +144,18 @@ class AvailableCommandsPacket extends DataPacket{
return $retval;
}
protected function getSoftEnum() : CommandEnum{
$retval = new CommandEnum();
$retval->enumName = $this->getString();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
//Get the enum value from the initial pile of mess
$retval->enumValues[] = $this->getString();
}
return $retval;
}
protected function putEnum(CommandEnum $enum){
$this->putString($enum->enumName);
@ -147,6 +170,15 @@ class AvailableCommandsPacket extends DataPacket{
}
}
protected function putSoftEnum(CommandEnum $enum) : void{
$this->putString($enum->enumName);
$this->putUnsignedVarInt(count($enum->enumValues));
foreach($enum->enumValues as $value){
$this->putString($value);
}
}
protected function getEnumValueIndex() : int{
if($this->enumValuesCount < 256){
return $this->getByte();
@ -185,13 +217,17 @@ class AvailableCommandsPacket extends DataPacket{
if($parameter->paramType & self::ARG_FLAG_ENUM){
$index = ($parameter->paramType & 0xffff);
$parameter->enum = $this->enums[$index] ?? null;
assert($parameter->enum !== null, "expected enum at $index, but got none");
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){ //postfix (guessing)
if($parameter->enum === null){
throw new \UnexpectedValueException("expected enum at $index, but got none");
}
}elseif($parameter->paramType & self::ARG_FLAG_POSTFIX){
$index = ($parameter->paramType & 0xffff);
$parameter->postfix = $this->postfixes[$index] ?? null;
assert($parameter->postfix !== null, "expected postfix at $index, but got none");
if($parameter->postfix === null){
throw new \UnexpectedValueException("expected postfix at $index, but got none");
}
}elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
throw new \UnexpectedValueException("Invalid parameter type 0x" . dechex($parameter->paramType));
}
$retval->overloads[$overloadIndex][$paramIndex] = $parameter;
@ -227,7 +263,7 @@ class AvailableCommandsPacket extends DataPacket{
if($key === false){
throw new \InvalidStateException("Postfix '$parameter->postfix' not in postfixes array");
}
$type = $parameter->paramType << 24 | $key;
$type = self::ARG_FLAG_POSTFIX | $key;
}else{
$type = $parameter->paramType;
}
@ -266,13 +302,12 @@ class AvailableCommandsPacket extends DataPacket{
case self::ARG_TYPE_COMMAND:
return "command";
}
}elseif($argtype !== 0){
//guessed
$baseType = $argtype >> 24;
$typeName = $this->argTypeToString(self::ARG_FLAG_VALID | $baseType);
}elseif($argtype & self::ARG_FLAG_POSTFIX){
$postfix = $this->postfixes[$argtype & 0xffff];
return $typeName . " (postfix $postfix)";
return "int (postfix $postfix)";
}else{
throw new \UnexpectedValueException("Unknown arg type 0x" . dechex($argtype));
}
return "unknown ($argtype)";
@ -334,6 +369,11 @@ class AvailableCommandsPacket extends DataPacket{
foreach($this->commandData as $data){
$this->putCommandData($data);
}
$this->putUnsignedVarInt(count($this->softEnums));
foreach($this->softEnums as $enum){
$this->putSoftEnum($enum);
}
}
public function handle(NetworkSession $session) : bool{

View File

@ -74,10 +74,6 @@ abstract class DataPacket extends NetworkBinaryStream{
protected function decodeHeader(){
$pid = $this->getUnsignedVarInt();
assert($pid === static::NETWORK_ID);
$this->senderSubId = $this->getByte();
$this->recipientSubId = $this->getByte();
assert($this->senderSubId === 0 and $this->recipientSubId === 0, "Got unexpected non-zero split-screen bytes (byte1: $this->senderSubId, byte2: $this->recipientSubId");
}
/**
@ -96,9 +92,6 @@ abstract class DataPacket extends NetworkBinaryStream{
protected function encodeHeader(){
$this->putUnsignedVarInt(static::NETWORK_ID);
$this->putByte($this->senderSubId);
$this->putByte($this->recipientSubId);
}
/**

View File

@ -40,6 +40,9 @@ class EventPacket extends DataPacket{
public const TYPE_BOSS_KILLED = 7;
public const TYPE_AGENT_COMMAND = 8;
public const TYPE_AGENT_CREATED = 9;
public const TYPE_PATTERN_REMOVED = 10; //???
public const TYPE_COMMANED_EXECUTED = 11;
public const TYPE_FISH_BUCKETED = 12;
/** @var int */
public $playerRuntimeId;

View File

@ -79,13 +79,6 @@ class LoginPacket extends DataPacket{
protected function decodePayload(){
$this->protocol = $this->getInt();
if($this->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
if($this->protocol > 0xffff){ //guess MCPE <= 1.1
$this->offset -= 6;
$this->protocol = $this->getInt();
}
}
try{
$this->decodeConnectionRequest();
}catch(\Throwable $e){

View File

@ -0,0 +1,47 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class NetworkStackLatencyPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::NETWORK_STACK_LATENCY_PACKET;
/** @var int */
public $timestamp;
protected function decodePayload(){
$this->timestamp = $this->getLLong();
}
protected function encodePayload(){
$this->putLLong($this->timestamp);
}
public function handle(NetworkSession $session) : bool{
return $session->handleNetworkStackLatency($this);
}
}

View File

@ -142,7 +142,11 @@ class PacketPool{
static::registerPacket(new LabTablePacket());
static::registerPacket(new UpdateBlockSyncedPacket());
static::registerPacket(new MoveEntityDeltaPacket());
static::registerPacket(new SetScoreboardIdentityPacket());
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
static::registerPacket(new UpdateSoftEnumPacket());
static::registerPacket(new NetworkStackLatencyPacket());
static::registerPacket(new ScriptCustomEventPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -43,12 +43,6 @@ class PlayStatusPacket extends DataPacket{
/** @var int */
public $status;
/**
* @var int
* Used to determine how to write the packet when we disconnect incompatible clients.
*/
public $protocol = ProtocolInfo::CURRENT_PROTOCOL;
protected function decodePayload(){
$this->status = $this->getInt();
}
@ -57,14 +51,6 @@ class PlayStatusPacket extends DataPacket{
return true;
}
protected function encodeHeader(){
if($this->protocol < 130){ //MCPE <= 1.1
$this->putByte(static::NETWORK_ID);
}else{
parent::encodeHeader();
}
}
protected function encodePayload(){
$this->putInt($this->status);
}

View File

@ -56,8 +56,6 @@ class PlayerListPacket extends DataPacket{
$entry->uuid = $this->getUUID();
$entry->entityUniqueId = $this->getEntityUniqueId();
$entry->username = $this->getString();
$entry->thirdPartyName = $this->getString();
$entry->platform = $this->getVarInt();
$skinId = $this->getString();
$skinData = $this->getString();
@ -90,8 +88,6 @@ class PlayerListPacket extends DataPacket{
$this->putUUID($entry->uuid);
$this->putEntityUniqueId($entry->entityUniqueId);
$this->putString($entry->username);
$this->putString($entry->thirdPartyName);
$this->putVarInt($entry->platform);
$this->putString($entry->skin->getSkinId());
$this->putString($entry->skin->getSkinData());
$this->putString($entry->skin->getCapeData());

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 274;
public const CURRENT_PROTOCOL = 291;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.5.0';
public const MINECRAFT_VERSION = 'v1.7.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.5.0';
public const MINECRAFT_VERSION_NETWORK = '1.7.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -160,6 +160,10 @@ interface ProtocolInfo{
public const LAB_TABLE_PACKET = 0x6d;
public const UPDATE_BLOCK_SYNCED_PACKET = 0x6e;
public const MOVE_ENTITY_DELTA_PACKET = 0x6f;
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x70;
public const SET_SCOREBOARD_IDENTITY_PACKET = 0x70;
public const SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71;
public const UPDATE_SOFT_ENUM_PACKET = 0x72;
public const NETWORK_STACK_LATENCY_PACKET = 0x73;
public const SCRIPT_CUSTOM_EVENT_PACKET = 0x75;
}

View File

@ -42,20 +42,20 @@ class ResourcePackStackPacket extends DataPacket{
public $resourcePackStack = [];
protected function decodePayload(){
/*$this->mustAccept = $this->getBool();
$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getUnsignedVarInt();
while($behaviorPackCount-- > 0){
$packId = $this->getString();
$version = $this->getString();
$this->behaviorPackStack[] = new ResourcePackInfoEntry($packId, $version);
$this->getString();
$this->getString();
$this->getString();
}
$resourcePackCount = $this->getUnsignedVarInt();
while($resourcePackCount-- > 0){
$packId = $this->getString();
$version = $this->getString();
$this->resourcePackStack[] = new ResourcePackInfoEntry($packId, $version);
}*/
$this->getString();
$this->getString();
$this->getString();
}
}
protected function encodePayload(){

View File

@ -40,24 +40,26 @@ class ResourcePacksInfoPacket extends DataPacket{
public $resourcePackEntries = [];
protected function decodePayload(){
/*$this->mustAccept = $this->getBool();
$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getLShort();
while($behaviorPackCount-- > 0){
$id = $this->getString();
$version = $this->getString();
$size = $this->getLLong();
$this->behaviorPackEntries[] = new ResourcePackInfoEntry($id, $version, $size);
$this->getString();
$this->getString();
$this->getLLong();
$this->getString();
$this->getString();
$this->getString();
}
$resourcePackCount = $this->getLShort();
while($resourcePackCount-- > 0){
$id = $this->getString();
$version = $this->getString();
$size = $this->getLLong();
$this->resourcePackEntries[] = new ResourcePackInfoEntry($id, $version, $size);
$this->getString();
}*/
$this->getString();
$this->getLLong();
$this->getString();
$this->getString();
$this->getString();
}
}
protected function encodePayload(){
@ -70,6 +72,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putLLong($entry->getPackSize());
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
}
$this->putLShort(count($this->resourcePackEntries));
foreach($this->resourcePackEntries as $entry){
@ -78,6 +81,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putLLong($entry->getPackSize());
$this->putString(""); //TODO: encryption key
$this->putString(""); //TODO: subpack name
$this->putString(""); //TODO: content identity
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class ScriptCustomEventPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SCRIPT_CUSTOM_EVENT_PACKET;
/** @var string */
public $eventName;
/** @var string json data */
public $eventData;
protected function decodePayload(){
$this->eventName = $this->getString();
$this->eventData = $this->getString();
}
protected function encodePayload(){
$this->putString($this->eventName);
$this->putString($this->eventData);
}
public function handle(NetworkSession $session) : bool{
return $session->handleScriptCustomEvent($this);
}
}

View File

@ -31,8 +31,8 @@ use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;
class SetScorePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SET_SCORE_PACKET;
public const TYPE_MODIFY_SCORE = 0;
public const TYPE_RESET_SCORE = 1;
public const TYPE_CHANGE = 0;
public const TYPE_REMOVE = 1;
/** @var int */
public $type;
@ -43,9 +43,23 @@ class SetScorePacket extends DataPacket{
$this->type = $this->getByte();
for($i = 0, $i2 = $this->getUnsignedVarInt(); $i < $i2; ++$i){
$entry = new ScorePacketEntry();
$entry->uuid = $this->getUUID();
$entry->scoreboardId = $this->getVarLong();
$entry->objectiveName = $this->getString();
$entry->score = $this->getLInt();
if($this->type !== self::TYPE_REMOVE){
$entry->type = $this->getByte();
switch($entry->type){
case ScorePacketEntry::TYPE_PLAYER:
case ScorePacketEntry::TYPE_ENTITY:
$entry->entityUniqueId = $this->getEntityUniqueId();
break;
case ScorePacketEntry::TYPE_FAKE_PLAYER:
$entry->customName = $this->getString();
break;
default:
throw new \UnexpectedValueException("Unknown entry type $entry->type");
}
}
}
}
@ -53,9 +67,23 @@ class SetScorePacket extends DataPacket{
$this->putByte($this->type);
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$this->putUUID($entry->uuid);
$this->putVarLong($entry->scoreboardId);
$this->putString($entry->objectiveName);
$this->putLInt($entry->score);
if($this->type !== self::TYPE_REMOVE){
$this->putByte($entry->type);
switch($entry->type){
case ScorePacketEntry::TYPE_PLAYER:
case ScorePacketEntry::TYPE_ENTITY:
$this->putEntityUniqueId($entry->entityUniqueId);
break;
case ScorePacketEntry::TYPE_FAKE_PLAYER:
$this->putString($entry->customName);
break;
default:
throw new \UnexpectedValueException("Unknown entry type $entry->type");
}
}
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\ScoreboardIdentityPacketEntry;
class SetScoreboardIdentityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SET_SCOREBOARD_IDENTITY_PACKET;
public const TYPE_REGISTER_IDENTITY = 0;
public const TYPE_CLEAR_IDENTITY = 1;
/** @var int */
public $type;
/** @var ScoreboardIdentityPacketEntry[] */
public $entries = [];
protected function decodePayload(){
$this->type = $this->getByte();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$entry = new ScoreboardIdentityPacketEntry();
$entry->scoreboardId = $this->getVarLong();
if($this->type === self::TYPE_REGISTER_IDENTITY){
$entry->entityUniqueId = $this->getEntityUniqueId();
}
$this->entries[] = $entry;
}
}
protected function encodePayload(){
$this->putByte($this->type);
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$this->putVarLong($entry->scoreboardId);
if($this->type === self::TYPE_REGISTER_IDENTITY){
$this->putEntityUniqueId($entry->entityUniqueId);
}
}
}
public function handle(NetworkSession $session) : bool{
return $session->handleSetScoreboardIdentity($this);
}
}

View File

@ -27,12 +27,16 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
class StartGamePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET;
/** @var string|null */
private static $runtimeIdTable;
/** @var int */
public $entityUniqueId;
/** @var int */
@ -112,6 +116,8 @@ class StartGamePacket extends DataPacket{
public $hasLockedResourcePack = false;
/** @var bool */
public $isFromLockedWorldTemplate = false;
/** @var bool */
public $useMsaGamertagsOnly = false;
/** @var string */
public $levelId = ""; //base64 string, usually the same as world folder name in vanilla
@ -125,6 +131,8 @@ class StartGamePacket extends DataPacket{
public $currentTick = 0; //only used if isTrial is true
/** @var int */
public $enchantmentSeed = 0;
/** @var string */
public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort
protected function decodePayload(){
$this->entityUniqueId = $this->getEntityUniqueId();
@ -167,6 +175,7 @@ class StartGamePacket extends DataPacket{
$this->hasLockedBehaviorPack = $this->getBool();
$this->hasLockedResourcePack = $this->getBool();
$this->isFromLockedWorldTemplate = $this->getBool();
$this->useMsaGamertagsOnly = $this->getBool();
$this->levelId = $this->getString();
$this->worldName = $this->getString();
@ -175,6 +184,14 @@ class StartGamePacket extends DataPacket{
$this->currentTick = $this->getLLong();
$this->enchantmentSeed = $this->getVarInt();
$count = $this->getUnsignedVarInt();
for($i = 0; $i < $count; ++$i){
$this->getString();
$this->getLShort();
}
$this->multiplayerCorrelationId = $this->getString();
}
protected function encodePayload(){
@ -218,6 +235,7 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->hasLockedBehaviorPack);
$this->putBool($this->hasLockedResourcePack);
$this->putBool($this->isFromLockedWorldTemplate);
$this->putBool($this->useMsaGamertagsOnly);
$this->putString($this->levelId);
$this->putString($this->worldName);
@ -226,6 +244,21 @@ class StartGamePacket extends DataPacket{
$this->putLLong($this->currentTick);
$this->putVarInt($this->enchantmentSeed);
if(self::$runtimeIdTable === null){
//this is a really nasty hack, but it'll do for now
$stream = new NetworkBinaryStream();
$data = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
$stream->putUnsignedVarInt(count($data));
foreach($data as $v){
$stream->putString($v["name"]);
$stream->putLShort($v["data"]);
}
self::$runtimeIdTable = $stream->buffer;
}
$this->put(self::$runtimeIdTable);
$this->putString($this->multiplayerCorrelationId);
}
public function handle(NetworkSession $session) : bool{

View File

@ -48,10 +48,6 @@ class TextPacket extends DataPacket{
/** @var string */
public $sourceName;
/** @var string */
public $sourceThirdPartyName = "";
/** @var int */
public $sourcePlatform = 0;
/** @var string */
public $message;
/** @var string[] */
public $parameters = [];
@ -69,8 +65,6 @@ class TextPacket extends DataPacket{
/** @noinspection PhpMissingBreakStatementInspection */
case self::TYPE_ANNOUNCEMENT:
$this->sourceName = $this->getString();
$this->sourceThirdPartyName = $this->getString();
$this->sourcePlatform = $this->getVarInt();
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:
@ -101,8 +95,6 @@ class TextPacket extends DataPacket{
/** @noinspection PhpMissingBreakStatementInspection */
case self::TYPE_ANNOUNCEMENT:
$this->putString($this->sourceName);
$this->putString($this->sourceThirdPartyName);
$this->putVarInt($this->sourcePlatform);
case self::TYPE_RAW:
case self::TYPE_TIP:
case self::TYPE_SYSTEM:

View File

@ -0,0 +1,64 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class UpdateSoftEnumPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::UPDATE_SOFT_ENUM_PACKET;
public const TYPE_ADD = 0;
public const TYPE_REMOVE = 1;
public const TYPE_SET = 2;
/** @var string */
public $enumName;
/** @var string[] */
public $values = [];
/** @var int */
public $type;
protected function decodePayload(){
$this->enumName = $this->getString();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->values[] = $this->getString();
}
$this->type = $this->getByte();
}
protected function encodePayload(){
$this->putString($this->enumName);
$this->putUnsignedVarInt(count($this->values));
foreach($this->values as $v){
$this->putString($v);
}
$this->putByte($this->type);
}
public function handle(NetworkSession $session) : bool{
return $session->handleUpdateSoftEnum($this);
}
}

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