Compare commits

..

130 Commits
3.0.6 ... 3.1.7

Author SHA1 Message Date
6e6cda91ce Release 3.1.7 2018-09-11 11:45:17 +01:00
69500fe183 LightUpdate: Remove garbage left over from dab73d8950 2018-09-11 11:35:31 +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
c447d51e3f Bucket: use ItemFactory instead of self-clone
in the future Item->setDamage() will be removed.
2018-09-06 18:42:09 +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
3a85e6cab9 Backport ce58294305 for 3.x line 2018-08-30 15:46:26 +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
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
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
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
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
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
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
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
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
d99ee515c6 3.1.5 is next 2018-08-16 18:33:27 +01:00
17f7dc34be Release 3.1.4 2018-08-16 18:26:49 +01:00
a63d66c048 Sync 3.1 and 3.0 branches 2018-08-16 18:26:26 +01:00
95f6995ae0 3.0.12 is next 2018-08-16 18:25:31 +01:00
4a24d7909e Release 3.0.11 2018-08-16 18:14:04 +01:00
4e2387edc1 Merge branch 'release/3.0' into release/3.1 2018-08-16 15:52:12 +01:00
a5e38576ef ItemEntity: fixed using -1 for infinite pickup delay not working
closes #2382 (squash-merge)
2018-08-16 15:52:05 +01:00
381151dedc Merge branch 'release/3.0' into release/3.1 2018-08-15 13:48:36 +01:00
a604e6835e CoalOre: fixed ignoring silk touch for XP drops, closes #2374 2018-08-15 13:48:29 +01:00
142a6d7678 Merge branch 'release/3.0' into release/3.1 2018-08-14 15:03:22 +01:00
b2ca364de0 SplashPotion: Don't apply effects to entities which are not alive
fixes #2372
2018-08-14 15:03:15 +01:00
09ed40a921 Merge branch 'release/3.0' into release/3.1 2018-08-13 13:22:32 +01:00
565373cee6 OfflinePlayer: remove unnecessary getName() usages 2018-08-13 13:22:00 +01:00
c29723e3c4 OfflinePlayer: remove unnecessary strtolower() calls
closes #2371
2018-08-13 13:18:58 +01:00
a8811ab2b3 Fixed 1.5.0 PlayerSkinPacket protocol change that somehow disappeared
I am 100% sure I committed this change, but it isn't in the merge...
2018-08-11 19:59:44 +01:00
974583a853 Merge branch 'release/3.0' into release/3.1 2018-08-11 19:37:10 +01:00
03f8fe62d4 Fixed structure of GuiDataPickItemPacket
this changed in 1.2.0.7 beta and I didn't spot it.
2018-08-11 19:36:53 +01:00
699f35cc05 Merge branch 'release/3.0' into release/3.1 2018-08-07 18:49:06 +01:00
8fa196efc9 FallingBlock: fixed state not being saved 2018-08-07 18:48:52 +01:00
b1ab881b99 Merge branch 'release/3.0' into release/3.1 2018-08-07 14:32:28 +01:00
69c54e789a Clear the title bar when the server shutdown. 2018-08-07 14:31:39 +01:00
e33d1279fa Merge branch 'release/3.0' into release/3.1 2018-08-06 18:45:01 +01:00
9e1fa453ad Level: Fixed leak of global packets when no players are online
If a global packet was broadcasted when no players were online, it would be held in memory indefinitely (until a player joined).
2018-08-06 18:44:53 +01:00
6a05edb4e9 Merge branch 'release/3.0' into release/3.1 2018-08-05 11:33:02 +01:00
70635d0870 DropItemAction: Consider invalid if the target item is null
it's not possible to drop a null item.
2018-08-05 11:32:50 +01:00
46bd096f06 3.1.4 is next 2018-08-04 16:46:51 +01:00
51a8905fb3 Release 3.1.3 2018-08-04 16:41:16 +01:00
f954d7c3dc Bring 3.1 up to speed with 3.0 2018-08-04 16:40:40 +01:00
7ad0aa56b1 3.0.11 is next 2018-08-04 16:39:53 +01:00
1ff6f8846e disable dev flag 2018-08-04 16:30:23 +01:00
e6f53cc56b Merge branch 'release/3.0' into release/3.1 2018-08-03 20:07:46 +01:00
87f458f9bd AsyncPool: remove now-unnecessary isTerminated() call 2018-08-03 20:07:37 +01:00
5a7e575c3a AsyncPool: isCrashed() now returns true when a fatal error occurred
the fix for chunks earlier didn't fix...
2018-08-03 20:06:41 +01:00
20b37d0208 Merge branch 'release/3.0' into release/3.1 2018-08-03 18:50:14 +01:00
d6d98183ea MainLogger: Log messages and exception traces in a synchronized block
this ensures that stack traces are emitted coherently without messages from other threads landing in the middle.
2018-08-03 18:50:06 +01:00
89cf76363f Merge branch 'release/3.0' into release/3.1 2018-08-03 18:24:36 +01:00
9ff5c65fb6 Level: Make async chunk sending aware of faults
Previously any random error could occur during an AsyncTask preparing a chunk, and the Level would never know about it and thus never send the chunk.

I don't know how many invisible-chunk bug cases this fixes, but I expect it's quite a lot.
2018-08-03 18:23:32 +01:00
1532b0ef6d Level: Remove chunks from chunk send queue on unload
When a chunk request task crashes, these can get stuck and never get removed. This allows using /gc to collect the bad chunk in order to fix the bug.
2018-08-03 18:04:56 +01:00
61accee682 Merge branch 'release/3.0' into release/3.1 2018-08-02 14:43:44 +01:00
9ece971a2b Server: remove useless check from exceptionHandler()
this cannot be null... @shoghicp y u litter the code with these useless checks ???
2018-08-02 14:41:28 +01:00
5546c88f88 Server: Fixed parse errors getting reported to CA
this changed to throwing errors as of PHP 7
2018-08-02 14:40:36 +01:00
4c4761d200 back to dev 2018-07-30 15:21:10 +01:00
5492495d38 disable dev flag 2018-07-30 15:10:12 +01:00
6bef07db7c Empty merge of 3.0 into 3.1 2018-07-30 15:09:53 +01:00
e8c7ae595d back to dev 2018-07-30 15:08:32 +01:00
0d9f40873f disable dev flag 2018-07-30 14:57:51 +01:00
f7358cd7e1 Merge branch 'release/3.0' into release/3.1 2018-07-30 14:54:01 +01:00
a4aee98cba TimingsCommand: some code cleanup 2018-07-30 14:53:10 +01:00
a97c7d3132 Fix for timings 2018-07-30 14:42:16 +01:00
808d289610 Merge branch 'release/3.0' into release/3.1 2018-07-27 11:47:22 +01:00
4a1ed21e52 PluginManager: Fix patch level check to allow loading the plugin when the server's minor level is higher than the plugins declared minor level. 2018-07-27 11:46:24 +01:00
06c035bfe6 Merge branch 'release/3.0' into release/3.1 2018-07-26 14:40:24 +01:00
1b053c7928 Clean up pointless checks in Thread/Worker 2018-07-26 14:20:55 +01:00
c684f99cc4 Clean up Thread/Worker quit() 2018-07-26 14:17:01 +01:00
8d47a222b4 Merge branch 'release/3.0' into release/3.1 2018-07-26 10:25:13 +01:00
695793795e PluginManager: Remove dead $pluginParentTimer left over from 9e4d88a852 2018-07-26 10:25:01 +01:00
9f425bbe2b Merge branch 'release/3.0' into release/3.1 2018-07-25 15:30:31 +01:00
a4965842d6 Remove $handlerList from PlayerExperienceChangeEvent 2018-07-25 15:30:01 +01:00
d0339796b4 Added DATA_FLAG_SHOW_TRIDENT_ROPE 2018-07-24 17:19:06 +01:00
5e13e2e777 Merge branch 'release/3.0' into release/3.1 2018-07-21 09:53:31 +01:00
1ef6f5d166 ZippedResourcePack: Make manifest parse errors less useless 2018-07-21 09:53:16 +01:00
eccc249009 KillCommand: clean up old shitcode 2018-07-20 19:44:41 +01:00
4be36914d6 back to dev 2018-07-20 12:21:15 +01:00
e3ef1ecb30 another empty merge 2018-07-20 12:20:54 +01:00
dbaf7287bc back to dev 2018-07-20 12:20:24 +01:00
3640062142 disable dev flag 2018-07-20 12:12:26 +01:00
9af70283fd Empty merge 2018-07-20 12:11:58 +01:00
b3b240e25b disable dev flag 2018-07-20 12:05:14 +01:00
b18872fbc6 Merge branch 'release/3.0' into release/3.1 2018-07-20 11:57:06 +01:00
2b30ef1671 Revert "Living: fix knockback condition, take 2"
This reverts commit 0081e30a89.

The logic introduced by this commit is correct in MC JAVA 1.9+. Unfortunately, nobody likes 1.9+ for combat.
Some testing in MCPE vanilla made it apparent that this logic isn't correct for MCPE. The old logic is correct for pre-1.9 knockback.
2018-07-20 11:55:10 +01:00
dd8499e202 Merge branch 'release/3.0' into release/3.1 2018-07-20 11:30:27 +01:00
124ebf69c5 PlayStatusPacket: default to current protocol if not specified 2018-07-20 11:29:40 +01:00
4d1e56069d Merge branch 'release/3.0' into release/3.1 2018-07-18 15:14:27 +01:00
4274640845 Player: fixed on-ground state not being updated when walking horizontally
it's possible to walk off a tower while flying without moving vertically, and this code previously wouldn't detect that, leaving a gaping hole in the anti-cheat.
2018-07-18 15:14:18 +01:00
45d30d53cc back to dev 2018-07-17 18:33:36 +01:00
cfc8dfa369 disable dev flag 2018-07-17 18:21:02 +01:00
93a2f397c6 Merge branch 'mc-broken-ed-1.5' into release/3.1 2018-07-17 18:13:06 +01:00
62fc875cdc bump version 2018-07-17 18:12:49 +01:00
58b665985e back to dev 2018-07-17 18:09:24 +01:00
0f5c48e342 Disable dev flag for release 2018-07-17 16:59:00 +01:00
08ad5db05b Config: remove useless switch cases
CNF is the same type as PROPERTIES (it's an alias) so these cases are useless.
2018-07-17 16:56:47 +01:00
94e8623c75 Server: account for default provider being missing 2018-07-17 12:14:26 +01:00
921f7e8f6a Level: remove useless check from populateChunk()
this is already checked at the top of the function.
2018-07-16 17:36:36 +01:00
710e1d014d Entity: fixed 0-length motion vectors being passed to move()
this was an interesting bug.

This was discovered by making a projectile's drag 0, making its gravity a factor of its throw force (such that force / gravity = integer value), and then throwing it directly up. At the apex, an error would occur due to trying to do a ray trace with a zero vector.

This also led me to realize that there's an edge case in the current movement system - if an entity's motion reaches 0, it will stop getting movement updates. This can be undesirable when things such as gravity cause motion to become zero when throwing a projectile directly upwards. This will need to be fixed separately.
2018-07-16 12:08:13 +01:00
165aac1ba3 Merge branch 'release/3.0' into mc-broken-ed-1.5 2018-07-14 16:09:57 +01:00
7fc22d3227 Entity: fixed setNameTagAlwaysVisible()
mojang >.<

this doesn't fix the problem of invisibility making nametags hidden though.
2018-07-14 16:05:46 +01:00
7bfe487ee5 ConcretePowder: fixed a missed usage of Block::get() 2018-07-14 10:35:05 +01:00
d8cf835f92 BlockFactory: better handling for dodgy IDs
I thought I'd already dealt with this, but it seems not.
2018-07-13 12:31:22 +01:00
65e44364e5 Added some debug for raw packets and Query handling 2018-07-13 10:07:11 +01:00
ebbbc581ca Player: clean up cursor inventory when closing main inventory 2018-07-12 17:52:22 +01:00
8aa8280a63 Level: Make spawn protection always active regardless of op count (#2290)
I don't care if this matches PC behaviour or not. bugs.mojang.com is full of bug reports about this. Just search for "minecraft spawn protection not working" and you'll see what I mean.

If you want to disable spawn protection, actually disable it. This behaviour is something that most users are not aware of and find astonishing when they discover it.

This behaviour was copied from Minecraft PC, and it's nearly as unexpected there as it is here.

This commit reverses the stupidity done in eb0525e892.
2018-07-12 17:25:05 +01:00
6a637d9099 update pthreads version for travis 2018-07-12 17:23:52 +01:00
4a5ff32d2e hacks for NPC and floating text
I didn't think mojang could break this fucking game any worse
2018-07-11 19:45:48 +01:00
06b80a9536 Level: Make getSafeSpawn() account for non-generated chunks
fixes #2295

There is still an issue in that the spawn point will not be offset if the chunk is not generated, but this is better than the spawn point being down at y=0. The other issue is a job for another time.
2018-07-11 10:17:59 +01:00
b5dcdea6d8 Protocol changes for 1.5.0 "release"
what a piece of shit this version is...
2018-07-11 10:00:15 +01:00
b3ffce9729 back to dev 2018-07-11 09:14:38 +01:00
76 changed files with 1115 additions and 395 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

@ -6,9 +6,9 @@ php:
before_script:
# - pecl install channel://pecl.php.net/pthreads-3.1.6
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
- git clone https://github.com/krakjoe/pthreads.git
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout d32079fb4a88e6e008104d36dbbf0c2dd7deb403
- git checkout c8cfacda84f21032d6014b53e72bf345ac901dac
- phpize
- ./configure
- make

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"
},

36
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.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c"
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
"reference": "da19487ff92f6f7a16b5ce8894132bb1d1e9ea0c",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/a4ee39f313c6870153fb7c7e513b211217f7daf8",
"reference": "a4ee39f313c6870153fb7c7e513b211217f7daf8",
"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.1",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2018-06-13T09:56:00+00:00"
"time": "2018-09-04T10:36:02+00:00"
},
{
"name": "pocketmine/raklib",

View File

@ -44,7 +44,7 @@ class OfflinePlayer implements IPlayer, Metadatable{
public function __construct(Server $server, string $name){
$this->server = $server;
$this->name = $name;
if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->getName()) . ".dat")){
if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->name) . ".dat")){
$this->namedtag = $this->server->getOfflinePlayerData($this->name);
}else{
$this->namedtag = null;
@ -64,7 +64,7 @@ class OfflinePlayer implements IPlayer, Metadatable{
}
public function isOp() : bool{
return $this->server->isOp(strtolower($this->getName()));
return $this->server->isOp($this->name);
}
public function setOp(bool $value){
@ -73,38 +73,38 @@ class OfflinePlayer implements IPlayer, Metadatable{
}
if($value){
$this->server->addOp(strtolower($this->getName()));
$this->server->addOp($this->name);
}else{
$this->server->removeOp(strtolower($this->getName()));
$this->server->removeOp($this->name);
}
}
public function isBanned() : bool{
return $this->server->getNameBans()->isBanned(strtolower($this->getName()));
return $this->server->getNameBans()->isBanned($this->name);
}
public function setBanned(bool $value){
if($value){
$this->server->getNameBans()->addBan($this->getName(), null, null, null);
$this->server->getNameBans()->addBan($this->name, null, null, null);
}else{
$this->server->getNameBans()->remove($this->getName());
$this->server->getNameBans()->remove($this->name);
}
}
public function isWhitelisted() : bool{
return $this->server->isWhitelisted(strtolower($this->getName()));
return $this->server->isWhitelisted($this->name);
}
public function setWhitelisted(bool $value){
if($value){
$this->server->addWhitelist(strtolower($this->getName()));
$this->server->addWhitelist($this->name);
}else{
$this->server->removeWhitelist(strtolower($this->getName()));
$this->server->removeWhitelist($this->name);
}
}
public function getPlayer(){
return $this->server->getPlayerExact($this->getName());
return $this->server->getPlayerExact($this->name);
}
public function getFirstPlayed(){

View File

@ -1487,14 +1487,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
protected function checkGroundState(float $movX, float $movY, float $movZ, float $dx, float $dy, float $dz) : void{
if(!$this->onGround or $movY != 0){
$bb = clone $this->boundingBox;
$bb->minY = $this->y - 0.2;
$bb->maxY = $this->y + 0.2;
$bb = clone $this->boundingBox;
$bb->minY = $this->y - 0.2;
$bb->maxY = $this->y + 0.2;
$this->onGround = count($this->level->getCollisionBlocks($bb, true)) > 0;
}
$this->isCollided = $this->onGround;
$this->onGround = $this->isCollided = count($this->level->getCollisionBlocks($bb, true)) > 0;
}
public function canBeMovedByCurrents() : bool{
@ -2168,7 +2165,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return false;
}
$this->resetCraftingGridType();
$this->doCloseInventory();
$message = TextFormat::clean($message, $this->removeFormat);
foreach(explode("\n", $message) as $messagePart){
@ -2243,7 +2240,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(!$this->spawned or !$this->isAlive()){
return true;
}
$this->resetCraftingGridType();
$this->doCloseInventory();
switch($packet->event){
case EntityEventPacket::EATING_ITEM:
@ -2394,7 +2391,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
case InventoryTransactionPacket::USE_ITEM_ACTION_BREAK_BLOCK:
$this->resetCraftingGridType();
$this->doCloseInventory();
$item = $this->inventory->getItemInHand();
$oldItem = clone $item;
@ -2635,7 +2632,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
$this->resetCraftingGridType();
$this->doCloseInventory();
$target = $this->level->getEntity($packet->target);
if($target === null){
@ -2849,7 +2846,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
$this->resetCraftingGridType();
$this->doCloseInventory();
if(isset($this->windowIndex[$packet->windowId])){
$this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this));
@ -2899,7 +2896,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(!$this->spawned or !$this->isAlive()){
return true;
}
$this->resetCraftingGridType();
$this->doCloseInventory();
$pos = new Vector3($packet->x, $packet->y, $packet->z);
if($pos->distanceSquared($this) > 10000 or $this->level->checkSpawnProtection($this, $pos)){
@ -3617,7 +3614,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
//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->resetCraftingGridType();
$this->doCloseInventory();
$this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)));
@ -3800,15 +3797,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->craftingGrid = $grid;
}
public function resetCraftingGridType() : void{
$contents = $this->craftingGrid->getContents();
if(count($contents) > 0){
$drops = $this->inventory->addItem(...$contents);
foreach($drops as $drop){
$this->dropItem($drop);
}
public function doCloseInventory() : void{
/** @var Inventory[] $inventories */
$inventories = [$this->craftingGrid, $this->cursorInventory];
foreach($inventories as $inventory){
$contents = $inventory->getContents();
if(count($contents) > 0){
$drops = $this->inventory->addItem(...$contents);
foreach($drops as $drop){
$this->dropItem($drop);
}
$this->craftingGrid->clearAll();
$inventory->clearAll();
}
}
if($this->craftingGrid->getGridWidth() > CraftingGrid::SIZE_SMALL){

View File

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

View File

@ -1026,6 +1026,7 @@ class Server{
}
try{
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
}catch(\Throwable $e){
@ -1070,6 +1071,9 @@ class Server{
if(($providerClass = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "pmanvil"))) === null){
$providerClass = LevelProviderManager::getProviderByName("pmanvil");
if($providerClass === null){
throw new \InvalidStateException("Default level provider has not been registered");
}
}
try{
@ -1077,6 +1081,7 @@ class Server{
/** @var LevelProvider $providerClass */
$providerClass::generate($path, $name, $seed, $generator, $options);
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
@ -2010,6 +2015,10 @@ class Server{
return;
}
if($this->doTitleTick){
echo "\x1b]0;\x07";
}
try{
if(!$this->isRunning()){
$this->sendUsage(SendUsageTask::TYPE_CLOSE);
@ -2141,10 +2150,6 @@ class Server{
* @param array|null $trace
*/
public function exceptionHandler(\Throwable $e, $trace = null){
if($e === null){
return;
}
global $lastError;
if($trace === null){
@ -2202,7 +2207,7 @@ class Server{
}
}
if($dump->getData()["error"]["type"] === "E_PARSE" or $dump->getData()["error"]["type"] === "E_COMPILE_ERROR"){
if($dump->getData()["error"]["type"] === \ParseError::class){
$report = false;
}
@ -2351,37 +2356,29 @@ class Server{
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);
}
}
}
@ -2471,6 +2468,8 @@ class Server{
try{
if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){
$this->queryHandler->handle($interface, $address, $port, $payload);
}else{
$this->logger->debug("Unhandled raw packet from $address $port: " . bin2hex($payload));
}
}catch(\Throwable $e){
if(\pocketmine\DEBUG > 1){

View File

@ -67,14 +67,10 @@ abstract class Thread extends \Thread{
public function start(?int $options = \PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
if($this->getClassLoader() === null){
$this->setClassLoader();
}
return parent::start($options);
if($this->getClassLoader() === null){
$this->setClassLoader();
}
return false;
return parent::start($options);
}
/**
@ -83,12 +79,9 @@ abstract class Thread extends \Thread{
public function quit(){
$this->isKilled = true;
$this->notify();
if(!$this->isJoined()){
if(!$this->isTerminated()){
$this->join();
}
$this->notify();
$this->join();
}
ThreadManager::getInstance()->remove($this);

View File

@ -67,14 +67,10 @@ abstract class Worker extends \Worker{
public function start(?int $options = \PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){
if($this->getClassLoader() === null){
$this->setClassLoader();
}
return parent::start($options);
if($this->getClassLoader() === null){
$this->setClassLoader();
}
return false;
return parent::start($options);
}
/**
@ -83,16 +79,10 @@ abstract class Worker extends \Worker{
public function quit(){
$this->isKilled = true;
$this->notify();
if($this->isRunning()){
$this->shutdown();
while($this->unstack() !== null);
$this->notify();
$this->unstack();
}elseif(!$this->isJoined()){
if(!$this->isTerminated()){
$this->join();
}
$this->shutdown();
}
ThreadManager::getInstance()->remove($this);

View File

@ -25,7 +25,6 @@ namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\level\Position;
use pocketmine\utils\MainLogger;
/**
* Manages block registration and instance creation
@ -436,19 +435,12 @@ class BlockFactory{
* @return int
*/
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
if($id === Block::AIR){
//TODO: HACK! (weird air blocks with non-zero damage values shouldn't turn into update! blocks)
$meta = 0;
}
$index = ($id << 4) | $meta;
if(!isset(self::$staticRuntimeIdMap[$index])){
self::registerMapping($rtId = ++self::$lastRuntimeId, $id, $meta);
MainLogger::getLogger()->error("ID $id meta $meta does not have a corresponding block static runtime ID, added a new unknown runtime ID ($rtId)");
return $rtId;
}
return self::$staticRuntimeIdMap[$index];
/*
* try id+meta first
* if not found, try id+0 (strip meta)
* if still not found, return update! block
*/
return self::$staticRuntimeIdMap[($id << 4) | $meta] ?? self::$staticRuntimeIdMap[$id << 4] ?? self::$staticRuntimeIdMap[BlockIds::INFO_UPDATE << 4];
}
/**

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

@ -57,7 +57,7 @@ class CoalOre extends Solid{
];
}
public function getXpDropForTool(Item $item) : int{
protected function getXpDropAmount() : int{
return mt_rand(0, 2);
}
}

View File

@ -66,7 +66,7 @@ class ConcretePowder extends Fallable{
private function checkAdjacentWater() : ?Block{
for($i = 1; $i < 6; ++$i){ //Do not check underneath
if($this->getSide($i) instanceof Water){
return Block::get(Block::CONCRETE, $this->meta);
return BlockFactory::get(Block::CONCRETE, $this->meta);
}
}

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

@ -62,15 +62,7 @@ class KillCommand extends VanillaCommand{
$player = $sender->getServer()->getPlayer($args[0]);
if($player instanceof Player){
$sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
if($ev->isCancelled()){
return true;
}
$player->setLastDamageCause($ev);
$player->setHealth(0);
$player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.kill.successful", [$player->getName()]));
}else{
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound"));
@ -86,14 +78,7 @@ class KillCommand extends VanillaCommand{
return true;
}
$sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000));
if($ev->isCancelled()){
return true;
}
$sender->setLastDamageCause($ev);
$sender->setHealth(0);
$sender->attack(new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000));
$sender->sendMessage(new TranslationContainer("commands.kill.successful", [$sender->getName()]));
}else{
throw new InvalidCommandSyntaxException();

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

@ -98,21 +98,33 @@ class TimingsCommand extends VanillaCommand{
if($paste){
fseek($fileTimings, 0);
$data = [
"syntax" => "text",
"poster" => $sender->getServer()->getName(),
"content" => stream_get_contents($fileTimings)
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings)
];
fclose($fileTimings);
$sender->getServer()->getAsyncPool()->submitTask(new class([
["page" => "http://paste.ubuntu.com", "extraOpts" => [
CURLOPT_HTTPHEADER => ["User-Agent: " . $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion()],
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $data,
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]]
], $sender) extends BulkCurlTask{
$host = $sender->getServer()->getProperty("timings.host", "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new class($sender, $host, $agent, $data) extends BulkCurlTask{
/** @var string */
private $host;
public function __construct(CommandSender $sender, string $host, string $agent, array $data){
parent::__construct([
["page" => "https://$host?upload=true", "extraOpts" => [
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]]
], $sender);
$this->host = $host;
}
public function onCompletion(Server $server){
$sender = $this->fetchLocal();
if($sender instanceof Player and !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
@ -123,23 +135,14 @@ class TimingsCommand extends VanillaCommand{
$server->getLogger()->logException($result);
return;
}
list(, $headers) = $result;
foreach($headers as $headerGroup){
if(isset($headerGroup["location"]) and preg_match('#^http://paste\\.ubuntu\\.com/([A-Za-z0-9+\/=]+)/#', trim($headerGroup["location"]), $match)){
$pasteId = $match[1];
break;
}
}
if(isset($pasteId)){
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $pasteId . "/"]));
if(isset($result[0]) && is_array($response = json_decode($result[0], true)) && isset($response["id"])){
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead",
["http://" . $sender->getServer()->getProperty("timings.host", "timings.pmmp.io") . "/?url=" . urlencode($pasteId)]));
["https://" . $this->host . "/?id=" . $response["id"]]));
}else{
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
}
}
});
}else{
fclose($fileTimings);
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));

View File

@ -64,7 +64,7 @@ use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\SetEntityDataPacket;
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
@ -169,7 +169,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_LIMITED_LIFE = 77;
public const DATA_ARMOR_STAND_POSE_INDEX = 78; //int
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; //int
/* 80 (byte) something to do with nametag visibility? */
public const DATA_ALWAYS_SHOW_NAMETAG = 80; //byte: -1 = default, 0 = only when looked at, 1 = always
public const DATA_COLOR_2 = 81; //byte
/* 82 (unknown) */
public const DATA_SCORE_TAG = 83; //string
@ -228,7 +228,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_FIRE_IMMUNE = 48;
public const DATA_FLAG_DANCING = 49;
public const DATA_FLAG_ENCHANTED = 50;
//51 is something to do with tridents
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;
@ -285,6 +285,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 +408,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;
@ -606,7 +605,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param bool $value
*/
public function setNameTagAlwaysVisible(bool $value = true) : void{
$this->setGenericFlag(self::DATA_FLAG_ALWAYS_SHOW_NAMETAG, $value);
$this->propertyManager->setByte(self::DATA_ALWAYS_SHOW_NAMETAG, $value ? 1 : 0);
}
/**
@ -1015,8 +1014,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 +1025,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
}
$this->age += $tickDiff;
$this->ticksLived += $tickDiff;
return $hasUpdate;
@ -1134,13 +1132,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
protected function broadcastMovement(bool $teleport = false) : void{
if($this->chunk !== null){
$pk = new MoveEntityPacket();
$pk = new MoveEntityAbsolutePacket();
$pk->entityRuntimeId = $this->id;
$pk->position = $this->getOffsetPosition($this);
$pk->yaw = $this->yaw;
$pk->pitch = $this->pitch;
$pk->headYaw = $this->yaw; //TODO
$pk->teleported = $teleport;
//this looks very odd but is correct as of 1.5.0.7
//for arrows this is actually x/y/z rotation
//for mobs x and z are used for pitch and yaw, and y is used for headyaw
$pk->xRot = $this->pitch;
$pk->yRot = $this->yaw; //TODO: head yaw
$pk->zRot = $this->yaw;
if($teleport){
$pk->flags |= MoveEntityAbsolutePacket::FLAG_TELEPORT;
}
$this->level->addChunkPacket($this->chunk->getX(), $this->chunk->getZ(), $pk);
}
@ -1346,7 +1351,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
if($this->hasMovementUpdate()){
$this->tryChangeMovement();
$this->move($this->motion->x, $this->motion->y, $this->motion->z);
if(abs($this->motion->x) <= self::MOTION_THRESHOLD){
$this->motion->x = 0;
@ -1358,6 +1362,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->motion->z = 0;
}
if($this->motion->x != 0 or $this->motion->y != 0 or $this->motion->z != 0){
$this->move($this->motion->x, $this->motion->y, $this->motion->z);
}
$this->forceMovementUpdate = false;
}

View File

@ -49,7 +49,9 @@ use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\EntityEventPacket;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\Player;
use pocketmine\utils\UUID;
@ -798,6 +800,14 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
if(!($this instanceof Player)){
/* 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)];
$player->dataPacket($pk);
}
$pk = new AddPlayerPacket();
$pk->uuid = $this->getUniqueId();
$pk->username = $this->getName();
@ -816,7 +826,10 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$this->armorInventory->sendContents($player);
if(!($this instanceof Player)){
$this->sendSkin([$player]);
$pk = new PlayerListPacket();
$pk->type = PlayerListPacket::TYPE_REMOVE;
$pk->entries = [PlayerListEntry::createRemovalEntry($this->uuid)];
$player->dataPacket($pk);
}
}

View File

@ -575,17 +575,14 @@ abstract class Living extends Entity implements Damageable{
$motion = clone $this->motion;
$motion->x /= 2;
$motion->y /= 2;
$motion->z /= 2;
$motion->x += $x * $f * $base;
$motion->y += $base;
$motion->z += $z * $f * $base;
if($this->onGround){
$motion->y /= 2;
$motion->y += $base;
if($motion->y > 0.4){ //this is hardcoded in vanilla
$motion->y = 0.4;
}
if($motion->y > $base){
$motion->y = $base;
}
$this->setMotion($motion);
@ -632,7 +629,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()){
@ -644,6 +643,7 @@ abstract class Living extends Entity implements Damageable{
if(!$this->canBreathe()){
$this->setBreathing(false);
$this->doAirSupplyTick($tickDiff);
$hasUpdate = true;
}elseif(!$this->isBreathing()){
$this->setBreathing(true);
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
@ -659,7 +659,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)){
@ -670,6 +670,8 @@ abstract class Living extends Entity implements Damageable{
$this->removeEffect($instance->getId());
}
}
return !empty($this->effects);
}
/**

View File

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

View File

@ -134,6 +134,7 @@ class FallingBlock extends Entity{
}
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setInt("TileID", $this->block->getId(), true);
$this->namedtag->setByte("Data", $this->block->getDamage());
}

View File

@ -53,6 +53,9 @@ class ItemEntity extends Entity{
public $canCollide = false;
/** @var int */
protected $age = 0;
protected function initEntity() : void{
parent::initEntity();
@ -82,14 +85,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 +101,6 @@ class ItemEntity extends Entity{
$hasUpdate = true;
}
}
}
return $hasUpdate;
@ -197,7 +198,7 @@ class ItemEntity extends Entity{
}
public function onCollideWithPlayer(Player $player) : void{
if($this->getPickupDelay() > 0){
if($this->getPickupDelay() !== 0){
return;
}

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

@ -48,11 +48,24 @@ class Arrow extends Projectile{
protected $damage = 2;
/** @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->collideTicks = $this->namedtag->getShort("life", $this->collideTicks);
}
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setShort("life", $this->collideTicks);
}
public function isCritical() : bool{
return $this->getGenericFlag(self::DATA_FLAG_CRITICAL);
}
@ -77,9 +90,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;

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

@ -73,7 +73,6 @@ abstract class Projectile extends Entity{
$this->setMaxHealth(1);
$this->setHealth(1);
$this->age = $this->namedtag->getShort("Age", $this->age);
do{
$blockHit = null;
@ -123,8 +122,6 @@ abstract class Projectile extends Entity{
public function saveNBT() : void{
parent::saveNBT();
$this->namedtag->setShort("Age", $this->age);
if($this->blockHit !== null){
$this->namedtag->setInt("tileX", $this->blockHit->x);
$this->namedtag->setInt("tileY", $this->blockHit->y);

View File

@ -82,7 +82,7 @@ class SplashPotion extends Throwable{
if($hasEffects){
if(!$this->willLinger()){
foreach($this->level->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
if($entity instanceof Living){
if($entity instanceof Living and $entity->isAlive()){
$distanceSquared = $entity->distanceSquared($this);
if($distanceSquared > 16){ //4 blocks
continue;
@ -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

@ -31,7 +31,6 @@ use pocketmine\event\entity\EntityEvent;
* Called when a player gains or loses XP levels and/or progress.
*/
class PlayerExperienceChangeEvent extends EntityEvent implements Cancellable{
public static $handlerList = null;
/** @var Human */
protected $entity;
/** @var int */

View File

@ -38,7 +38,7 @@ class DropItemAction extends InventoryAction{
}
public function isValid(Player $source) : bool{
return true;
return !$this->targetItem->isNull();
}
public function onPreExecute(Player $source) : bool{

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

@ -191,7 +191,7 @@ class Level implements ChunkManager, Metadatable{
/** @var Player[][] */
private $chunkSendQueue = [];
/** @var bool[] */
/** @var ChunkRequestTask[] */
private $chunkSendTasks = [];
/** @var bool[] */
@ -797,8 +797,10 @@ class Level implements ChunkManager, Metadatable{
$this->checkSleep();
}
if(!empty($this->players) and !empty($this->globalPackets)){
$this->server->batchPackets($this->players, $this->globalPackets);
if(!empty($this->globalPackets)){
if(!empty($this->players)){
$this->server->batchPackets($this->players, $this->globalPackets);
}
$this->globalPackets = [];
}
@ -1627,7 +1629,7 @@ class Level implements ChunkManager, Metadatable{
$spawnLocation = $this->getSpawnLocation();
$s = new Vector2($spawnLocation->x, $spawnLocation->z);
if(count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance){
if($t->distance($s) <= $distance){
return true;
}
}
@ -2456,7 +2458,7 @@ class Level implements ChunkManager, Metadatable{
}
private function sendChunkFromCache(int $x, int $z){
if(isset($this->chunkSendTasks[$index = Level::chunkHash($x, $z)])){
if(isset($this->chunkSendQueue[$index = Level::chunkHash($x, $z)])){
foreach($this->chunkSendQueue[$index] as $player){
/** @var Player $player */
if($player->isConnected() and isset($player->usedChunks[$index])){
@ -2464,7 +2466,6 @@ class Level implements ChunkManager, Metadatable{
}
}
unset($this->chunkSendQueue[$index]);
unset($this->chunkSendTasks[$index]);
}
}
@ -2473,11 +2474,17 @@ class Level implements ChunkManager, Metadatable{
$this->timings->syncChunkSendTimer->startTiming();
foreach($this->chunkSendQueue as $index => $players){
if(isset($this->chunkSendTasks[$index])){
continue;
}
Level::getXZ($index, $x, $z);
$this->chunkSendTasks[$index] = true;
if(isset($this->chunkSendTasks[$index])){
if($this->chunkSendTasks[$index]->isCrashed()){
unset($this->chunkSendTasks[$index]);
$this->server->getLogger()->error("Failed to prepare chunk $x $z for sending, retrying");
}else{
//Not ready for sending yet
continue;
}
}
if(isset($this->chunkCache[$index])){
$this->sendChunkFromCache($x, $z);
continue;
@ -2490,7 +2497,8 @@ class Level implements ChunkManager, Metadatable{
}
assert($chunk->getX() === $x and $chunk->getZ() === $z, "Chunk coordinate mismatch: expected $x $z, but chunk has coordinates " . $chunk->getX() . " " . $chunk->getZ() . ", did you forget to clone a chunk before setting?");
$this->server->getAsyncPool()->submitTask(new ChunkRequestTask($this, $x, $z, $chunk));
$this->server->getAsyncPool()->submitTask($task = new ChunkRequestTask($this, $x, $z, $chunk));
$this->chunkSendTasks[$index] = $task;
$this->timings->syncChunkSendPrepareTimer->stopTiming();
}
@ -2503,24 +2511,14 @@ class Level implements ChunkManager, Metadatable{
$this->timings->syncChunkSendTimer->startTiming();
$index = Level::chunkHash($x, $z);
unset($this->chunkSendTasks[$index]);
if(!isset($this->chunkCache[$index]) and $this->server->getMemoryManager()->canUseChunkCache()){
$this->chunkCache[$index] = $payload;
$this->sendChunkFromCache($x, $z);
$this->timings->syncChunkSendTimer->stopTiming();
return;
$this->chunkCache[$index] = $payload;
$this->sendChunkFromCache($x, $z);
if(!$this->server->getMemoryManager()->canUseChunkCache()){
unset($this->chunkCache[$index]);
}
if(isset($this->chunkSendTasks[$index])){
foreach($this->chunkSendQueue[$index] as $player){
/** @var Player $player */
if($player->isConnected() and isset($player->usedChunks[$index])){
$player->sendChunk($x, $z, $payload);
}
}
unset($this->chunkSendQueue[$index]);
unset($this->chunkSendTasks[$index]);
}
$this->timings->syncChunkSendTimer->stopTiming();
}
@ -2754,6 +2752,8 @@ class Level implements ChunkManager, Metadatable{
unset($this->chunkCache[$chunkHash]);
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
unset($this->chunkSendQueue[$chunkHash]);
unset($this->chunkSendTasks[$chunkHash]);
$this->timings->doChunkUnload->stopTiming();
@ -2791,7 +2791,7 @@ class Level implements ChunkManager, Metadatable{
$chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false);
$x = (int) $v->x;
$z = (int) $v->z;
if($chunk !== null){
if($chunk !== null and $chunk->isGenerated()){
$y = (int) min($max - 2, $v->y);
$wasAir = ($chunk->getBlockId($x & 0x0f, $y - 1, $z & 0x0f) === 0);
for(; $y > 0; --$y){
@ -2947,16 +2947,14 @@ class Level implements ChunkManager, Metadatable{
}
if($populate){
if(!isset($this->chunkPopulationQueue[$index])){
$this->chunkPopulationQueue[$index] = true;
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
$this->chunkPopulationQueue[$index] = true;
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
$task = new PopulationTask($this, $chunk);
$this->server->getAsyncPool()->submitTask($task);
}
$task = new PopulationTask($this, $chunk);
$this->server->getAsyncPool()->submitTask($task);
}
Timings::$populationTimer->stopTiming();

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

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

@ -29,8 +29,9 @@ use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\utils\UUID;
class FloatingTextParticle extends Particle{
@ -89,9 +90,17 @@ class FloatingTextParticle extends Particle{
}
if(!$this->invisible){
$uuid = UUID::fromRandom();
$name = $this->title . ($this->text !== "" ? "\n" . $this->text : "");
$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)))];
$p[] = $add;
$pk = new AddPlayerPacket();
$pk->uuid = $uuid = UUID::fromRandom();
$pk->username = $this->title . ($this->text !== "" ? "\n" . $this->text : "");
$pk->uuid = $uuid;
$pk->username = $name;
$pk->entityRuntimeId = $this->entityId;
$pk->position = $this->asVector3(); //TODO: check offset
$pk->item = ItemFactory::get(Item::AIR, 0, 0);
@ -106,10 +115,10 @@ class FloatingTextParticle extends Particle{
$p[] = $pk;
$skinPk = new PlayerSkinPacket();
$skinPk->uuid = $uuid;
$skinPk->skin = new Skin("Standard_Custom", str_repeat("\x00", 8192));
$p[] = $skinPk;
$remove = new PlayerListPacket();
$remove->type = PlayerListPacket::TYPE_REMOVE;
$remove->entries = [PlayerListEntry::createRemovalEntry($uuid)];
$p[] = $remove;
}
return $p;

View File

@ -76,7 +76,8 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MoveEntityPacket;
use pocketmine\network\mcpe\protocol\MoveEntityAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveEntityDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
@ -111,6 +112,7 @@ use pocketmine\network\mcpe\protocol\SetEntityLinkPacket;
use pocketmine\network\mcpe\protocol\SetEntityMotionPacket;
use pocketmine\network\mcpe\protocol\SetHealthPacket;
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\SetSpawnPositionPacket;
@ -207,7 +209,7 @@ abstract class NetworkSession{
return false;
}
public function handleMoveEntity(MoveEntityPacket $packet) : bool{
public function handleMoveEntityAbsolute(MoveEntityAbsolutePacket $packet) : bool{
return false;
}
@ -578,4 +580,12 @@ abstract class NetworkSession{
public function handleUpdateBlockSynced(UpdateBlockSyncedPacket $packet) : bool{
return false;
}
public function handleMoveEntityDelta(MoveEntityDeltaPacket $packet) : bool{
return false;
}
public function handleSetLocalPlayerAsInitialized(SetLocalPlayerAsInitializedPacket $packet) : bool{
return false;
}
}

View File

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

@ -44,9 +44,11 @@ class AddEntityPacket extends DataPacket{
/** @var Vector3|null */
public $motion;
/** @var float */
public $pitch = 0.0;
/** @var float */
public $yaw = 0.0;
/** @var float */
public $pitch = 0.0;
public $headYaw = 0.0;
/** @var Attribute[] */
public $attributes = [];
@ -63,6 +65,7 @@ class AddEntityPacket extends DataPacket{
$this->motion = $this->getVector3();
$this->pitch = $this->getLFloat();
$this->yaw = $this->getLFloat();
$this->headYaw = $this->getLFloat();
$attrCount = $this->getUnsignedVarInt();
for($i = 0; $i < $attrCount; ++$i){
@ -97,6 +100,7 @@ class AddEntityPacket extends DataPacket{
$this->putVector3Nullable($this->motion);
$this->putLFloat($this->pitch);
$this->putLFloat($this->yaw);
$this->putLFloat($this->headYaw);
$this->putUnsignedVarInt(count($this->attributes));
foreach($this->attributes as $attribute){

View File

@ -29,6 +29,7 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\MapTrackedObject;
use pocketmine\utils\Color;
class ClientboundMapItemDataPacket extends DataPacket{
@ -49,8 +50,8 @@ class ClientboundMapItemDataPacket extends DataPacket{
/** @var int */
public $scale;
/** @var int[] */
public $decorationEntityUniqueIds = [];
/** @var MapTrackedObject[] */
public $trackedEntities = [];
/** @var array */
public $decorations = [];
@ -83,7 +84,16 @@ class ClientboundMapItemDataPacket extends DataPacket{
if(($this->type & self::BITFLAG_DECORATION_UPDATE) !== 0){
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->decorationEntityUniqueIds[] = $this->getEntityUniqueId();
$object = new MapTrackedObject();
$object->type = $this->getLInt();
if($object->type === MapTrackedObject::TYPE_BLOCK){
$this->getBlockPosition($object->x, $object->y, $object->z);
}elseif($object->type === MapTrackedObject::TYPE_ENTITY){
$object->entityUniqueId = $this->getEntityUniqueId();
}else{
throw new \UnexpectedValueException("Unknown map object type");
}
$this->trackedEntities[] = $object;
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
@ -143,9 +153,16 @@ class ClientboundMapItemDataPacket extends DataPacket{
}
if(($type & self::BITFLAG_DECORATION_UPDATE) !== 0){
$this->putUnsignedVarInt(count($this->decorationEntityUniqueIds));
foreach($this->decorationEntityUniqueIds as $id){
$this->putEntityUniqueId($id);
$this->putUnsignedVarInt(count($this->trackedEntities));
foreach($this->trackedEntities as $object){
$this->putLInt($object->type);
if($object->type === MapTrackedObject::TYPE_BLOCK){
$this->putBlockPosition($object->x, $object->y, $object->z);
}elseif($object->type === MapTrackedObject::TYPE_ENTITY){
$this->putEntityUniqueId($object->entityUniqueId);
}else{
throw new \UnexpectedValueException("Unknown map object type");
}
}
$this->putUnsignedVarInt($decorationCount);

View File

@ -30,14 +30,22 @@ use pocketmine\network\mcpe\NetworkSession;
class GuiDataPickItemPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::GUI_DATA_PICK_ITEM_PACKET;
/** @var string */
public $itemDescription;
/** @var string */
public $itemEffects;
/** @var int */
public $hotbarSlot;
protected function decodePayload(){
$this->itemDescription = $this->getString();
$this->itemEffects = $this->getString();
$this->hotbarSlot = $this->getLInt();
}
protected function encodePayload(){
$this->putString($this->itemDescription);
$this->putString($this->itemEffects);
$this->putLInt($this->hotbarSlot);
}

View File

@ -29,45 +29,44 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
class MoveEntityPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::MOVE_ENTITY_PACKET;
class MoveEntityAbsolutePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::MOVE_ENTITY_ABSOLUTE_PACKET;
public const FLAG_GROUND = 0x01;
public const FLAG_TELEPORT = 0x02;
/** @var int */
public $entityRuntimeId;
/** @var int */
public $flags = 0;
/** @var Vector3 */
public $position;
/** @var float */
public $yaw;
public $xRot;
/** @var float */
public $headYaw;
public $yRot;
/** @var float */
public $pitch;
/** @var bool */
public $onGround = false;
/** @var bool */
public $teleported = false;
public $zRot;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->flags = $this->getByte();
$this->position = $this->getVector3();
$this->pitch = $this->getByteRotation();
$this->headYaw = $this->getByteRotation();
$this->yaw = $this->getByteRotation();
$this->onGround = $this->getBool();
$this->teleported = $this->getBool();
$this->xRot = $this->getByteRotation();
$this->yRot = $this->getByteRotation();
$this->zRot = $this->getByteRotation();
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putByte($this->flags);
$this->putVector3($this->position);
$this->putByteRotation($this->pitch);
$this->putByteRotation($this->headYaw);
$this->putByteRotation($this->yaw);
$this->putBool($this->onGround);
$this->putBool($this->teleported);
$this->putByteRotation($this->xRot);
$this->putByteRotation($this->yRot);
$this->putByteRotation($this->zRot);
}
public function handle(NetworkSession $session) : bool{
return $session->handleMoveEntity($this);
return $session->handleMoveEntityAbsolute($this);
}
}

View File

@ -0,0 +1,104 @@
<?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 MoveEntityDeltaPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::MOVE_ENTITY_DELTA_PACKET;
public const FLAG_HAS_X = 0x01;
public const FLAG_HAS_Y = 0x02;
public const FLAG_HAS_Z = 0x04;
public const FLAG_HAS_ROT_X = 0x08;
public const FLAG_HAS_ROT_Y = 0x10;
public const FLAG_HAS_ROT_Z = 0x20;
/** @var int */
public $flags;
/** @var int */
public $xDiff = 0;
/** @var int */
public $yDiff = 0;
/** @var int */
public $zDiff = 0;
/** @var float */
public $xRot = 0.0;
/** @var float */
public $yRot = 0.0;
/** @var float */
public $zRot = 0.0;
private function maybeReadCoord(int $flag) : int{
if($this->flags & $flag){
return $this->getVarInt();
}
return 0;
}
private function maybeReadRotation(int $flag) : float{
if($this->flags & $flag){
return $this->getByteRotation();
}
return 0.0;
}
protected function decodePayload(){
$this->flags = $this->getByte();
$this->xDiff = $this->maybeReadCoord(self::FLAG_HAS_X);
$this->yDiff = $this->maybeReadCoord(self::FLAG_HAS_Y);
$this->zDiff = $this->maybeReadCoord(self::FLAG_HAS_Z);
$this->xRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_X);
$this->yRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Y);
$this->zRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Z);
}
private function maybeWriteCoord(int $flag, int $val) : void{
if($this->flags & $flag){
$this->putVarInt($val);
}
}
private function maybeWriteRotation(int $flag, float $val) : void{
if($this->flags & $flag){
$this->putByteRotation($val);
}
}
protected function encodePayload(){
$this->putByte($this->flags);
$this->maybeWriteCoord(self::FLAG_HAS_X, $this->xDiff);
$this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yDiff);
$this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zDiff);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_X, $this->xRot);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Y, $this->yRot);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Z, $this->zRot);
}
public function handle(NetworkSession $session) : bool{
return $session->handleMoveEntityDelta($this);
}
}

View File

@ -48,7 +48,7 @@ class PacketPool{
static::registerPacket(new AddItemEntityPacket());
static::registerPacket(new AddHangingEntityPacket());
static::registerPacket(new TakeItemEntityPacket());
static::registerPacket(new MoveEntityPacket());
static::registerPacket(new MoveEntityAbsolutePacket());
static::registerPacket(new MovePlayerPacket());
static::registerPacket(new RiderJumpPacket());
static::registerPacket(new UpdateBlockPacket());
@ -141,6 +141,8 @@ class PacketPool{
static::registerPacket(new SetScorePacket());
static::registerPacket(new LabTablePacket());
static::registerPacket(new UpdateBlockSyncedPacket());
static::registerPacket(new MoveEntityDeltaPacket());
static::registerPacket(new SetLocalPlayerAsInitializedPacket());
static::registerPacket(new BatchPacket());
}

View File

@ -47,7 +47,7 @@ class PlayStatusPacket extends DataPacket{
* @var int
* Used to determine how to write the packet when we disconnect incompatible clients.
*/
public $protocol;
public $protocol = ProtocolInfo::CURRENT_PROTOCOL;
protected function decodePayload(){
$this->status = $this->getInt();

View File

@ -40,7 +40,8 @@ class PlayerSkinPacket extends DataPacket{
public $newSkinName = "";
/** @var Skin */
public $skin;
/** @var bool */
public $premiumSkin = false;
protected function decodePayload(){
$this->uuid = $this->getUUID();
@ -54,6 +55,8 @@ class PlayerSkinPacket extends DataPacket{
$geometryData = $this->getString();
$this->skin = new Skin($skinId, $skinData, $capeData, $geometryModel, $geometryData);
$this->premiumSkin = $this->getBool();
}
protected function encodePayload(){
@ -66,6 +69,8 @@ class PlayerSkinPacket extends DataPacket{
$this->putString($this->skin->getCapeData());
$this->putString($this->skin->getGeometryName());
$this->putString($this->skin->getGeometryData());
$this->putBool($this->premiumSkin);
}
public function handle(NetworkSession $session) : bool{

View File

@ -39,15 +39,15 @@ interface ProtocolInfo{
/**
* Actual Minecraft: PE protocol version
*/
public const CURRENT_PROTOCOL = 261;
public const CURRENT_PROTOCOL = 274;
/**
* Current Minecraft PE version reported by the server. This is usually the earliest currently supported version.
*/
public const MINECRAFT_VERSION = 'v1.4.0';
public const MINECRAFT_VERSION = 'v1.5.0';
/**
* Version number sent to clients in ping responses.
*/
public const MINECRAFT_VERSION_NETWORK = '1.4.0';
public const MINECRAFT_VERSION_NETWORK = '1.5.0';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -66,7 +66,7 @@ interface ProtocolInfo{
public const ADD_ITEM_ENTITY_PACKET = 0x0f;
public const ADD_HANGING_ENTITY_PACKET = 0x10;
public const TAKE_ITEM_ENTITY_PACKET = 0x11;
public const MOVE_ENTITY_PACKET = 0x12;
public const MOVE_ENTITY_ABSOLUTE_PACKET = 0x12;
public const MOVE_PLAYER_PACKET = 0x13;
public const RIDER_JUMP_PACKET = 0x14;
public const UPDATE_BLOCK_PACKET = 0x15;
@ -159,5 +159,7 @@ interface ProtocolInfo{
public const SET_SCORE_PACKET = 0x6c;
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;
}

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 SetLocalPlayerAsInitializedPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET;
/** @var int */
public $entityRuntimeId;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
}
public function handle(NetworkSession $session) : bool{
return $session->handleSetLocalPlayerAsInitialized($this);
}
}

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\network\mcpe\protocol\types;
class MapTrackedObject{
public const TYPE_ENTITY = 0;
public const TYPE_BLOCK = 1;
/** @var int */
public $type;
/** @var int Only set if is TYPE_ENTITY */
public $entityUniqueId;
/** @var int */
public $x;
/** @var int */
public $y;
/** @var int */
public $z;
}

View File

@ -58,6 +58,11 @@ class QueryHandler{
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.server.query.running", [$addr, $port]));
}
private function debug(string $message) : void{
//TODO: replace this with a proper prefixed logger
$this->server->getLogger()->debug("[Query] $message");
}
public function regenerateInfo(){
$ev = $this->server->getQueryInformation();
$this->longData = $ev->getLongQuery();
@ -91,7 +96,8 @@ class QueryHandler{
break;
case self::STATISTICS: //Stat
$token = Binary::readInt(substr($payload, 0, 4));
if($token !== self::getTokenString($this->token, $address) and $token !== self::getTokenString($this->lastToken, $address)){
if($token !== ($t1 = self::getTokenString($this->token, $address)) and $token !== ($t2 = self::getTokenString($this->lastToken, $address))){
$this->debug("Bad token $token from $address $port, expected $t1 or $t2");
break;
}
$reply = chr(self::STATISTICS);
@ -108,6 +114,9 @@ class QueryHandler{
}
$interface->sendRawPacket($address, $port, $reply);
break;
default:
$this->debug("Unhandled packet from $address $port: 0x" . bin2hex($packet));
break;
}
}
}

View File

@ -177,7 +177,7 @@ class RCONInstance extends Thread{
$disconnect[$id] = $sock;
break;
}
if(strlen($payload) > 0){
if($payload !== ""){
$this->cmd = ltrim($payload);
$this->synchronized(function(){
$this->notifier->wakeupSleeper();

View File

@ -168,7 +168,7 @@ class BanEntry{
}
$expire = trim(array_shift($str));
if(strtolower($expire) !== "forever" and strlen($expire) > 0){
if($expire !== "" and strtolower($expire) !== "forever"){
$entry->setExpires(self::parseDate($expire));
}
if(empty($str)){

View File

@ -102,9 +102,6 @@ class PluginManager{
/** @var string|null */
private $pluginDataDirectory;
/** @var TimingsHandler */
public static $pluginParentTimer;
/**
* @param Server $server
* @param SimpleCommandMap $commandMap
@ -416,7 +413,7 @@ class PluginManager{
continue;
}
if($pluginNumbers[2] > $serverNumbers[2]){ //If the plugin requires bug fixes in patches, being backwards compatible
if($pluginNumbers[1] === $serverNumbers[1] and $pluginNumbers[2] > $serverNumbers[2]){ //If the plugin requires bug fixes in patches, being backwards compatible
continue;
}
}
@ -844,7 +841,7 @@ class PluginManager{
throw new PluginException("Plugin attempted to register " . $event . " while not enabled");
}
$timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")", self::$pluginParentTimer);
$timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")");
$this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled, $timings));
}

View File

@ -86,8 +86,11 @@ class ZippedResourcePack implements ResourcePack{
$archive->close();
$manifest = json_decode($manifestData);
if($manifest === null or !self::verifyManifest($manifest)){
throw new ResourcePackException("manifest.json is invalid or incomplete");
if($manifest === null){
throw new ResourcePackException("Failed to parse manifest.json: " . json_last_error_msg());
}
if(!self::verifyManifest($manifest)){
throw new ResourcePackException("manifest.json is missing required fields");
}
$this->manifest = $manifest;

File diff suppressed because one or more lines are too long

View File

@ -295,7 +295,7 @@ class AsyncPool{
}
$this->removeTask($task);
}elseif($task->isTerminated() or $task->isCrashed()){
}elseif($task->isCrashed()){
$this->logger->critical("Could not execute asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": Task crashed");
$this->removeTask($task, true);
}

View File

@ -80,7 +80,7 @@ abstract class AsyncTask extends Collectable{
}
public function isCrashed() : bool{
return $this->crashed;
return $this->crashed or $this->isTerminated();
}
/**

View File

@ -103,6 +103,7 @@ abstract class Tile extends Position{
public static function createTile($type, Level $level, CompoundTag $nbt, ...$args) : ?Tile{
if(isset(self::$knownTiles[$type])){
$class = self::$knownTiles[$type];
/** @see Tile::__construct() */
return new $class($level, $nbt, ...$args);
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\timings;
use pocketmine\entity\Entity;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\Player;
use pocketmine\plugin\PluginManager;
use pocketmine\scheduler\TaskHandler;
use pocketmine\tile\Tile;
@ -134,7 +133,7 @@ abstract class Timings{
self::$timerEntityBaseTick = new TimingsHandler("** entityBaseTick");
self::$timerLivingEntityBaseTick = new TimingsHandler("** livingEntityBaseTick");
self::$schedulerSyncTimer = new TimingsHandler("** Scheduler - Sync Tasks", PluginManager::$pluginParentTimer);
self::$schedulerSyncTimer = new TimingsHandler("** Scheduler - Sync Tasks");
self::$schedulerAsyncTimer = new TimingsHandler("** Scheduler - Async Tasks");
self::$playerCommandTimer = new TimingsHandler("** playerCommand");

View File

@ -111,7 +111,7 @@ class Config{
* @return string
*/
public static function fixYAMLIndexes(string $str) : string{
return preg_replace("#^([ ]*)([a-zA-Z_]{1}[ ]*)\\:$#m", "$1\"$2\":", $str);
return preg_replace("#^( *)(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)( *)\:#m", "$1\"$2\"$3:", $str);
}
/**
@ -144,7 +144,6 @@ class Config{
$content = file_get_contents($this->file);
switch($this->type){
case Config::PROPERTIES:
case Config::CNF:
$this->parseProperties($content);
break;
case Config::JSON:
@ -197,7 +196,6 @@ class Config{
$content = null;
switch($this->type){
case Config::PROPERTIES:
case Config::CNF:
$content = $this->writeProperties();
break;
case Config::JSON:

View File

@ -204,10 +204,16 @@ class MainLogger extends \AttachableThreadedLogger{
$errno = $errorConversion[$errno] ?? $errno;
$errstr = preg_replace('/\s+/', ' ', trim($errstr));
$errfile = Utils::cleanPath($errfile);
$this->log($type, get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline");
foreach(Utils::getTrace(0, $trace) as $i => $line){
$this->debug($line, true);
}
$message = get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
$stack = Utils::getTrace(0, $trace);
$this->synchronized(function() use ($type, $message, $stack) : void{
$this->log($type, $message);
foreach($stack as $line){
$this->debug($line, true);
}
});
$this->syncFlushBuffer();
}
@ -259,19 +265,22 @@ class MainLogger extends \AttachableThreadedLogger{
}
$message = sprintf($this->format, date("H:i:s", $now), $color, $threadName, $prefix, $message);
$cleanMessage = TextFormat::clean($message);
if($this->mainThreadHasFormattingCodes and Terminal::hasFormattingCodes()){ //hasFormattingCodes() lazy-inits colour codes because we don't know if they've been registered on this thread
echo Terminal::toANSI($message) . PHP_EOL;
}else{
echo $cleanMessage . PHP_EOL;
}
$this->synchronized(function() use ($message, $level, $now) : void{
$cleanMessage = TextFormat::clean($message);
foreach($this->attachments as $attachment){
$attachment->call($level, $message);
}
if($this->mainThreadHasFormattingCodes and Terminal::hasFormattingCodes()){ //hasFormattingCodes() lazy-inits colour codes because we don't know if they've been registered on this thread
echo Terminal::toANSI($message) . PHP_EOL;
}else{
echo $cleanMessage . PHP_EOL;
}
$this->logStream[] = date("Y-m-d", $now) . " " . $cleanMessage . PHP_EOL;
foreach($this->attachments as $attachment){
$attachment->call($level, $message);
}
$this->logStream[] = date("Y-m-d", $now) . " " . $cleanMessage . PHP_EOL;
});
}
public function syncFlushBuffer(){

View File

@ -0,0 +1,71 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use PHPUnit\Framework\TestCase;
class ConfigTest extends TestCase{
/**
* @return \Generator
*/
public function fixYamlIndexesProvider() : \Generator{
yield ["x: 1\ny: 2\nz: 3\n", [
"x" => 1,
"y" => 2,
"z" => 3
]];
yield [" x : 1\n y : 2\n z : 3\n", [
"x" => 1,
"y" => 2,
"z" => 3
]];
yield ["parent:\n x: 1\n y: 2\n z: 3\n", [
"parent" => [
"x" => 1,
"y" => 2,
"z" => 3
]
]];
yield ["yes: notransform", [
"yes" => "notransform"
]];
yield ["on: 1\nyes: true", [ //this would previously have resulted in a key collision
"on" => 1,
"yes" => true
]];
}
/**
* @dataProvider fixYamlIndexesProvider
*
* @param string $test
* @param array $expected
*/
public function testFixYamlIndexes(string $test, array $expected) : void{
$fixed = Config::fixYAMLIndexes($test);
$decoded = yaml_parse($fixed);
self::assertEquals($expected, $decoded);
}
}

View File

@ -0,0 +1,7 @@
name: TesterPlugin
main: pmmp\TesterPlugin\Main
version: 0.1.0
api: [3.0.0]
load: POSTWORLD
author: pmmp
description: Plugin used to run tests on PocketMine-MP

View File

@ -0,0 +1,50 @@
<?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 pmmp\TesterPlugin;
use pocketmine\scheduler\Task;
class CheckTestCompletionTask extends Task{
/** @var Main */
private $plugin;
public function __construct(Main $plugin){
$this->plugin = $plugin;
}
public function onRun(int $currentTick){
$test = $this->plugin->getCurrentTest();
if($test === null){
if(!$this->plugin->startNextTest()){
$this->plugin->getScheduler()->cancelTask($this->getHandler()->getTaskId());
$this->plugin->onAllTestsCompleted();
}
}elseif($test->isFinished() or $test->isTimedOut()){
$this->plugin->onTestCompleted($test);
}else{
$test->tick();
}
}
}

View File

@ -0,0 +1,109 @@
<?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 pmmp\TesterPlugin;
use pocketmine\event\Listener;
use pocketmine\event\server\ServerCommandEvent;
use pocketmine\plugin\PluginBase;
class Main extends PluginBase implements Listener{
/** @var Test[] */
protected $waitingTests = [];
/** @var Test|null */
protected $currentTest = null;
/** @var Test[] */
protected $completedTests = [];
/** @var int */
protected $currentTestNumber = 0;
public function onEnable(){
$this->getServer()->getPluginManager()->registerEvents($this, $this);
$this->getScheduler()->scheduleRepeatingTask(new CheckTestCompletionTask($this), 10);
$this->waitingTests = [
new tests\AsyncTaskMemoryLeakTest($this),
new tests\AsyncTaskMainLoggerTest($this)
];
}
public function onServerCommand(ServerCommandEvent $event){
//The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to
//run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may
//be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests
//have completed.
if($event->getCommand() === "stop"){
$event->setCancelled();
}
}
/**
* @return Test|null
*/
public function getCurrentTest(){
return $this->currentTest;
}
public function startNextTest() : bool{
$this->currentTest = array_shift($this->waitingTests);
if($this->currentTest !== null){
$this->getLogger()->notice("Running test #" . (++$this->currentTestNumber) . " (" . $this->currentTest->getName() . ")");
$this->currentTest->start();
return true;
}
return false;
}
public function onTestCompleted(Test $test){
$message = "Finished test #" . $this->currentTestNumber . " (" . $test->getName() . "): ";
switch($test->getResult()){
case Test::RESULT_OK:
$message .= "PASS";
break;
case Test::RESULT_FAILED:
$message .= "FAIL";
break;
case Test::RESULT_ERROR:
$message .= "ERROR";
break;
case Test::RESULT_WAITING:
$message .= "TIMEOUT";
break;
default:
$message .= "UNKNOWN";
break;
}
$this->getLogger()->notice($message);
$this->completedTests[$this->currentTestNumber] = $test;
$this->currentTest = null;
}
public function onAllTestsCompleted(){
$this->getLogger()->notice("All tests finished, stopping the server");
$this->getServer()->shutdown();
}
}

View File

@ -0,0 +1,87 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pmmp\TesterPlugin;
abstract class Test{
const RESULT_WAITING = -1;
const RESULT_OK = 0;
const RESULT_FAILED = 1;
const RESULT_ERROR = 2;
private $plugin;
private $result = Test::RESULT_WAITING;
private $startTime;
private $timeout = 60; //seconds
public function __construct(Main $plugin){
$this->plugin = $plugin;
}
public function getPlugin() : Main{
return $this->plugin;
}
final public function start(){
$this->startTime = time();
try{
$this->run();
}catch(TestFailedException $e){
$this->getPlugin()->getLogger()->error($e->getMessage());
$this->setResult(Test::RESULT_FAILED);
}catch(\Throwable $e){
$this->getPlugin()->getLogger()->logException($e);
$this->setResult(Test::RESULT_ERROR);
}
}
public function tick(){
}
abstract public function run();
public function isFinished() : bool{
return $this->result !== Test::RESULT_WAITING;
}
public function isTimedOut() : bool{
return !$this->isFinished() and time() - $this->timeout > $this->startTime;
}
protected function setTimeout(int $timeout){
$this->timeout = $timeout;
}
public function getResult() : int{
return $this->result;
}
public function setResult(int $result){
$this->result = $result;
}
abstract public function getName() : string;
abstract public function getDescription() : string;
}

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 pmmp\TesterPlugin;
class TestFailedException extends \Exception{
}

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 pmmp\TesterPlugin\tests;
use pmmp\TesterPlugin\Test;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\utils\MainLogger;
class AsyncTaskMainLoggerTest extends Test{
public function run(){
$this->getPlugin()->getServer()->getAsyncPool()->submitTask(new class($this) extends AsyncTask{
/** @var bool */
protected $success = false;
public function __construct(AsyncTaskMainLoggerTest $testObject){
$this->storeLocal($testObject);
}
public function onRun(){
ob_start();
MainLogger::getLogger()->info("Testing");
if(strpos(ob_get_contents(), "Testing") !== false){
$this->success = true;
}
ob_end_flush();
}
public function onCompletion(Server $server){
/** @var AsyncTaskMainLoggerTest $test */
$test = $this->fetchLocal();
$test->setResult($this->success ? Test::RESULT_OK : Test::RESULT_FAILED);
}
});
}
public function getName() : string{
return "MainLogger::getLogger() works in AsyncTasks";
}
public function getDescription() : string{
return "Verifies that the MainLogger is accessible by MainLogger::getLogger() in an AsyncTask";
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pmmp\TesterPlugin\tests;
use pmmp\TesterPlugin\Test;
use pocketmine\scheduler\AsyncTask;
class AsyncTaskMemoryLeakTest extends Test{
public function run(){
$this->getPlugin()->getServer()->getAsyncPool()->submitTask(new TestAsyncTask());
}
public function tick(){
if(TestAsyncTask::$destroyed === true){
$this->setResult(Test::RESULT_OK);
}
}
public function getName() : string{
return "AsyncTask memory leak after completion";
}
public function getDescription() : string{
return "Regression test for AsyncTasks objects not being destroyed after completion";
}
}
class TestAsyncTask extends AsyncTask{
public static $destroyed = false;
public function onRun(){
usleep(50 * 1000); //1 server tick
}
public function __destruct(){
self::$destroyed = true;
}
}

View File

@ -47,7 +47,7 @@ fi
mkdir "$DATA_DIR"
mkdir "$PLUGINS_DIR"
mv DevTools.phar "$PLUGINS_DIR"
cp -r tests/plugins/PocketMine-TesterPlugin "$PLUGINS_DIR"
cp -r tests/plugins/TesterPlugin "$PLUGINS_DIR"
echo -e "stop\n" | "$PHP_BINARY" PocketMine-MP.phar --no-wizard --disable-ansi --disable-readline --debug.level=2 --data="$DATA_DIR" --plugins="$PLUGINS_DIR" --anonymous-statistics.enabled=0 --settings.async-workers="$PM_WORKERS" --settings.enable-dev-builds=1
output=$(grep '\[TesterPlugin\]' "$DATA_DIR/server.log")