Compare commits

...

266 Commits
3.5.5 ... 3.8.4

Author SHA1 Message Date
a42f68d3cf Release 3.8.4 2019-06-10 17:55:50 +01:00
b6f3f6120b InventoryTransaction: Shuffle actions on execution to prevent plugins relying on the order
closes #2955
2019-06-10 17:24:16 +01:00
96d3f4f78b Updated documentation for transaction classes 2019-06-10 17:19:03 +01:00
6756203aec InventoryTransaction: Added a warning about ordering 2019-06-10 16:54:45 +01:00
adb78679c5 Added link to docker image 2019-06-09 18:47:04 +08:00
b60731607d Update travis.sh 2019-06-06 17:20:16 +01:00
0058bfcd23 Utils: Limit length of printed stack trace strings to 80 characters 2019-06-06 14:56:19 +01:00
9f31b479e1 Improve stack trace rendering, show array and string lengths 2019-06-06 14:53:50 +01:00
eb161f8e1c Use base64 instead of hex to display binary in some places
base64 takes less space than hex, which is beneficial for logging larger payloads.
2019-06-06 14:43:20 +01:00
80f8a27094 Fixed implicit dependency on MobArmorEquipmentPacket field ordering
the order of the slots is entirely dependent on Mojang whims, so we shouldn't let our internals rely on it.
2019-06-04 18:16:56 +01:00
341c480e13 ContainerSetDataPacket: added missing furnace property ID 2019-06-03 18:21:29 +01:00
971ad04299 Attribute: add more things
i got tired of commenting out exceptions in packets, so this is the quickest way to fix the problem.
2019-06-03 17:37:18 +01:00
025b72e2f2 Randomize the order of plugins retrieved from disk, fixes #2945 (#2948)
On most filesystems, plugins are loaded in lexical order because that's how the filesystem gives the files to us. This is a problem because it can hide bugs with dependency resolution on specific platforms with this behaviour, while inexplicably breaking on other platforms where the load order is different or undefined.

This change prevents plugins depending on any file yield order by randomizing the order in which plugin files are checked for loadability.
2019-05-31 17:11:20 +01:00
cf538afb84 LevelEventPacket: added two extra IDs 2019-05-31 15:31:03 +01:00
7ff91337ad 3.8.4 is next 2019-05-27 19:01:06 +01:00
63e5867727 Release 3.8.3 2019-05-27 18:59:56 +01:00
c38779f1fd Block: fixed use of full meta as variant in several cases
fixes #2940

now I need to go to the bathroom and wash this off my hands ... I'd forgotten how nasty this code is
2019-05-27 18:04:25 +01:00
14bae72dd5 Create FUNDING.yml 2019-05-27 14:14:09 +01:00
f35b43613d IronTrapdoor: fixed fuel time, closes #2914
this should be removed during branch merge.
2019-05-24 17:31:36 +01:00
3d0fa10437 Stonecutter: fix hardness, close #2915 2019-05-24 17:30:20 +01:00
76e5ea385b Skin: improved error checking
plugin devs should find this less of a pain in the ass now.
2019-05-17 17:40:27 +01:00
5a351d3caf Fix for Server::hasOfflinePlayerData (#2919) 2019-05-12 12:06:05 +01:00
bb93d4f8de TimeCommand: Fix commands.time.query message (#2917) 2019-05-12 09:21:39 +01:00
66818e178a DefaultPermissions: Fix typos in permission descriptions (#2918) 2019-05-12 09:20:45 +01:00
f782a478c4 Player: clarify resetTitles() documentation 2019-05-08 17:03:40 +01:00
c06b4830d1 Player: don't spawn entities on out-of-sync chunks on join, closes #2911 2019-05-08 15:36:54 +01:00
6d64ef8d74 Server: fixed crash reporting bug introduced by a6e5b6e158 2019-05-05 20:52:52 +01:00
c5498bb3fa Implement a locking variable to circumvent a infinite regression (#2901)
closes #2876
2019-05-03 14:46:51 +01:00
59334d652f 3.8.3 is next 2019-05-02 17:13:50 +01:00
5c81da9b1e Release 3.8.2 2019-05-02 17:13:21 +01:00
b08affc4c6 Updated preprocessor submodule 2019-05-02 17:11:40 +01:00
cb49f1e835 fixed air being breakable by creative players
closes #2851
2019-05-02 16:25:23 +01:00
f6a0f4aa82 Player: fixed immobile interference on PlayerJoinEvent
closes #2894

this should use a cleaner hack that doesn't affect plugins at all, but that's too much work for this one bug fix.
2019-05-02 16:20:28 +01:00
de09c8c082 Player: fix crash handling resource pack requests 2019-05-02 16:17:39 +01:00
0ac87989dc Update RakLib to 0.12.4 2019-05-02 16:08:03 +01:00
13ea984b12 Server: setup world things at a more appropriate time
closes #2859

this also fixes reported headaches attempting to override generators before world load.
2019-05-02 15:02:10 +01:00
3878f58847 Level: fixed crash when time overflows
this can be triggered by a plugin setting time to PHP_INT_MAX

fixes CA 2346977
2019-05-02 14:22:01 +01:00
d7a35a5302 Entity: fixed motion not being initialized when Motion NBT tag is missing
fixes CA 2355485
2019-05-02 14:00:28 +01:00
d850a84d0d Level: clean up and remove checkTime() 2019-04-26 14:15:41 +01:00
22d9260a3b remove unused import 2019-04-26 13:56:32 +01:00
3c90ed13b9 NoteBlock: added //TODO 2019-04-26 12:45:08 +01:00
396efbac7e Human: simplify setFood() 2019-04-26 12:40:15 +01:00
00644dd529 Fixed an edge-case in AvailableCommandsPacket decoding 2019-04-26 12:32:42 +01:00
c2c210e25a Fixed --enable-ansi and --disable-ansi not being respected on threads
this causes some breakage to the behaviour of Terminal, and for that reason this is going on 4.0.

Terminal::hasFormattingCodes() will no longer auto-detect whether colour codes are supported.
2019-04-26 12:30:24 +01:00
86c4e936cb Player now drops the contents of temporary inventories
these inventories are just glorified crafting tables.
2019-04-26 12:27:52 +01:00
3b103dcd62 Level: use isInWorld() where appropriate 2019-04-26 12:26:01 +01:00
48dfc5b232 Level: reduce complexity of populateChunk() 2019-04-26 12:18:00 +01:00
39360f127a Player: Don't use iusername in cases where it's useless
these calls all do strtolower() anyway, so having iusername here just makes it confusing.
2019-04-26 12:07:47 +01:00
25890e76e2 Player: replace redundant usage of Server->broadcast() 2019-04-26 12:04:56 +01:00
810bdeb965 Test one extra case for LevelProviderManager 2019-04-26 12:01:12 +01:00
b6b3dcc1aa Improve documentation of SourceInterface and AdvancedSourceInterface 2019-04-26 11:59:10 +01:00
e0818e7e52 reorganize and optimize start.sh (#2267) 2019-04-26 11:54:50 +01:00
d0c4463773 3.8.2 is next 2019-04-26 10:08:59 +01:00
7521160f53 Release 3.8.1 2019-04-26 10:08:22 +01:00
4f5190e3f8 Updated BedrockData submodule 2019-04-25 20:53:08 +01:00
3c941dd992 Move more stuff to BedrockData submodule 2019-04-25 18:23:31 +01:00
89f457b397 updated legacy ID table (thanks @MCMrARM) 2019-04-25 17:49:10 +01:00
2bdbb9794c New, more efficient blockstate table format
this reduces the file size by ~90%.
2019-04-25 17:28:22 +01:00
898009a91b Sync crafting data changes
this seems to fix #2881, at least the reproducing case that was given.
2019-04-25 16:22:12 +01:00
793f93afdb NetworkSession: sync with latest code generation changes 2019-04-25 14:49:20 +01:00
64506ea3ae PacketPool: add @throws 2019-04-25 14:48:55 +01:00
4a35516441 Reduce BatchPacket hacks
we can't get rid of these hacks entirely because BAcKWARdS ComPaTIbilitY, but this at least ensures that things over PID 127 won't burn the house down when 1.12 gets here. This also reduces conflicts with 4.0 line.
2019-04-25 14:48:36 +01:00
74b9922a28 3.8.1 is next 2019-04-24 20:00:58 +01:00
088fbf65b1 Release 3.8.0 2019-04-24 19:54:57 +01:00
fa70127241 1.11.0 protocol changes 2019-04-24 19:54:16 +01:00
fc76d04dcb StartGamePacket: allow specifying a custom runtimeID table
this is not the intended goal, but it's a happy side effect of making it easier to extract structured information from the client.
2019-04-24 18:40:29 +01:00
15ae323bcb doxygen: updated config file 2019-04-23 17:58:24 +01:00
353bae87a7 3.7.4 is next 2019-04-23 17:14:49 +01:00
dc7be3fd52 Release 3.7.3 2019-04-23 17:13:04 +01:00
f28a0740a0 Move version info to its own file for easier third party tooling 2019-04-23 16:07:25 +01:00
df990fb47a disable C preprocessing (this will just cause unwanted bugs) 2019-04-23 15:43:39 +01:00
98ca28d96e add a warning when chunkutils is missing 2019-04-21 17:36:38 +01:00
db896b9faf ok i'm done
i am obviously not in any fit state to be doing this today...
2019-04-21 14:22:54 +01:00
76c234e4e6 add some limits on batches 2019-04-21 14:16:38 +01:00
2ef3962028 sync RakLib dependency 2019-04-21 13:24:19 +01:00
1e5941c98c Level: Properly report world save timings
this wasn't including chunk unloading in the world save timings.
2019-04-18 19:39:23 +01:00
9ca7c0c883 3.7.3 is next 2019-04-15 18:45:53 +01:00
b4a8f8391b Release 3.7.2 2019-04-15 18:45:16 +01:00
142d750b9f TextFormat: clean() now removes private-use area Unicode characters
the console can't print these, and these are being abused to cause mass crashes in the wild.
2019-04-15 18:42:09 +01:00
948b0b4cbc Add an API change request template 2019-04-15 08:49:31 +01:00
d6b596a8ac moar issue templates 2019-04-15 08:49:16 +01:00
eab2d4d704 Add a Support issue template
maybe this will guide people who don't read into shooting themselves in the foot automatically >:)
2019-04-15 08:49:01 +01:00
8114551600 add a Crash issue template
valid crashdumps are usually self explanatory and include all required information like OS/version/etc, so there's no need to force reporters to rewrite a bunch of extra information that should already be in the crashdump.
2019-04-15 08:48:45 +01:00
8e1d1993c5 Update support.yml 2019-04-15 08:48:31 +01:00
82bf5f6193 Add Discord link to issue template 2019-04-15 08:48:16 +01:00
76a86e51f7 Remove legacy issue template 2019-04-15 08:47:58 +01:00
d1e803685a Update issue templates 2019-04-15 08:47:42 +01:00
0a884aa5fb Support bot: Be a little less rude 2019-04-15 08:47:32 +01:00
c410e676b3 Update ISSUE_TEMPLATE.md 2019-04-15 08:47:18 +01:00
8f1f5fde47 Security vulnerability reporting for dummies 2019-04-15 08:46:59 +01:00
0f268df2e0 Add community Discord to Discussion section 2019-04-14 22:57:13 +01:00
86108e7010 Added donation methods 2019-04-14 22:57:00 +01:00
5ea448ef36 Add ExamplePlugin to README 2019-04-14 22:56:49 +01:00
389e7767d1 Sign: apply a very stupid limit on text size
this is necessary because of the epidemic of server attacks going around lately.
2019-04-14 19:30:28 +01:00
4c268acc00 Fixed /whitelist on/off permissions, closes #2849 2019-04-14 17:58:53 +01:00
e34a4f6b98 Added server.lock into .gitignore
closes #2868
2019-04-13 14:26:47 +01:00
3925e598d6 Acquire exclusive lock on <datadir>/server.lock to avoid data corruption (#2858)
this fixes #2855.
2019-04-08 20:31:24 +01:00
383ec8a8e3 TextFormat: Fixed tokenize() not being Unicode-aware 2019-04-08 15:45:26 +01:00
95313e0a90 Updated some entity metadata properties 2019-04-08 14:42:33 +01:00
e9a87978a6 Fixed usages of commands.generic.permission
apparently this doesn't exist in the client anymore
2019-04-07 15:07:24 +01:00
612e9e162c Fixed worlds with unknown generators silently getting wrong generator, closes #2860 2019-04-06 15:17:36 +01:00
fdc3faadc9 Register two missing default permissions
this fixes the pocketmine.command node not having the expected effect in some cases.
2019-04-05 18:48:37 +01:00
9644e72acf Doxygen: added index page 2019-04-05 09:35:25 +01:00
2c678dcf0d Player: Fixed memory leak when PlayerLoginEvent is cancelled 2019-04-03 17:37:15 +01:00
7bd9a2b2e0 3.7.2 is next 2019-03-30 19:15:17 +00:00
5c26deb517 Release 3.7.1 2019-03-29 19:56:06 +00:00
8fcb44de7d Merge branch '3.6' into 3.7 2019-03-29 19:55:36 +00:00
bd2e7db3b9 Release 3.6.6 2019-03-29 19:49:30 +00:00
f4480c07ee Updated NBT dependency 2019-03-29 19:47:15 +00:00
c09e2301c8 Merge branch '3.6' into 3.7 2019-03-29 14:35:35 +00:00
8e9f787d33 Implement runtimeID table randomization, closes #2841 2019-03-29 14:35:06 +00:00
89833b3b68 Merge branch '3.6' into 3.7 2019-03-26 15:01:59 +00:00
a10a656a5d Network: add a README to mcpe namespace 2019-03-26 15:01:00 +00:00
2659ed8d91 AvailableCommandsPacket: Updated the argument type constants for 1.10. (#2824) 2019-03-22 15:07:15 +00:00
9c01ecbe7e Merge branch '3.6' into 3.7 2019-03-21 19:02:20 +00:00
6a55021779 Flat: Allow asterisk in preset layers
closes #2818
2019-03-21 18:59:50 +00:00
6ada261b04 3.7.1 is next 2019-03-20 19:39:36 +00:00
14a6779e08 Release 3.7.0 2019-03-20 19:17:41 +00:00
cce99b07af 1.10.0 updates 2019-03-20 19:17:41 +00:00
c3d80d711d Fixed autosave time report measurement not using correct unit (as per master) 2019-03-20 19:01:10 +00:00
1a8b33dafe AvailableCommandsPacket: Updated the ARG_TYPE constants
closes #2817
2019-03-18 08:58:07 +00:00
baa094a2d1 3.6.6 is next 2019-03-10 11:49:31 +00:00
f2ff510597 Release 3.6.5 2019-03-10 11:49:06 +00:00
562179bdd6 Backport 58cafc853f: s/level/world (strings only)
we should look at doing this for code too, but for now I'm not planning to break everyone's plugins.
2019-03-10 11:35:46 +00:00
5c12bee874 Backport other part of 2bffd5cc1c: Add timer measurements for autosave 2019-03-10 11:20:51 +00:00
99606bbe23 beware possible API break 2019-03-09 19:33:46 +00:00
a1d50de12e OOF 2019-03-09 19:31:13 +00:00
4252c5914b Backport 93cd00ae8f: Remove dead settings from pocketmine.yml 2019-03-09 19:21:39 +00:00
0659d2fbef Backport 6bd43a8215: Firehose auto-tick-rate anti-feature, closes #2665 2019-03-09 19:20:53 +00:00
10612acace Partial backport of 2bffd5cc1c to 3.6 2019-03-09 19:10:09 +00:00
1d810f8aeb Backport c3e66e0adc to 3.6 2019-03-09 19:09:05 +00:00
414104851a LevelDB: Mark chunks as changed when upgraded from an older chunk format 2019-03-09 19:02:34 +00:00
c0bed03a2a Update PlayerRespawnEvent.php (#2797)
removed obsolete comment
2019-03-05 16:28:58 +00:00
d25c84acff Fixed RegionLoader corrupting location table when too-large chunks are discovered
this was making the location table point to an offset that did not yet exist, which caused the region header consistency check to discard the region as corrupted the next time it was loaded.
2019-03-05 12:09:27 +00:00
55994e08db RegionLoader: make some error messages more detailed 2019-03-05 11:18:02 +00:00
6f5d4d6b80 RegionLoader: fixed handling of invalid chunk coordinates 2019-03-05 10:52:36 +00:00
df1ef7fe0c Improve RegionLoader tests 2019-03-05 10:51:44 +00:00
20a25a69df Force emission of output buffer contents on crash 2019-03-04 12:01:30 +00:00
faca610594 NetworkBinaryStream: remove dead field 2019-03-03 12:38:03 +00:00
91603dc2d6 3.6.5 is next 2019-03-03 11:50:10 +00:00
af90e18b18 Release 3.6.4 2019-03-03 11:49:07 +00:00
ab5b4d112b BaseInventory: fixed items with userdata stacking with items without userdata 2019-03-03 11:12:36 +00:00
a30b1fb6d5 Inventory: Add failing test case for itemstack NBT duplication 2019-03-03 11:11:33 +00:00
20b4723728 Player: fixed held slot being out of sync after dying, closes #2788
it appears this premature optimization dates back to the days when PlayerHotbarPacket was not useless.
2019-02-27 09:26:56 +00:00
d1ced0ffc6 Player: fixed XP not dropping on death 2019-02-27 09:22:52 +00:00
2164dbae67 Fixed reloaded arrows not despawning, closes #2781 2019-02-26 19:58:21 +00:00
6c92a2e88b Ladder: be more strict about resetting fall distance
closes #2790
2019-02-26 19:54:57 +00:00
97deadc59f PackedIce: fixed dropping without silk touch, closes #2789 2019-02-26 19:48:18 +00:00
0c3b136a8d Player: fixed removeWindow() sometimes removing GUI / crashing clients 2019-02-24 12:14:19 +00:00
79b7e08e60 Silence NetworkStackLatencyPacket spam from dev builds 2019-02-23 11:03:50 +00:00
2540dacdd7 PluginManager: fixed suffix split handling 2019-02-23 10:51:06 +00:00
f1078e3909 3.6.4 is next 2019-02-22 18:07:48 +00:00
2f43b054de Release 3.6.3 2019-02-22 18:07:00 +00:00
23b5d64535 Merge branch '3.5' into 3.6 2019-02-22 18:03:51 +00:00
9afa0e5483 Release 3.5.13 2019-02-22 17:56:44 +00:00
4eaea54b0e TaskScheduler: fixed wrong typehints
These methods never return null.
2019-02-22 17:47:28 +00:00
6b51bf4a80 Merge branch '3.5' into 3.6 2019-02-18 13:11:16 +00:00
cba8d86c4f Fixed Fire Aspect not working on TNT 2019-02-18 13:11:08 +00:00
2e834c8f5c Merge branch '3.5' into 3.6 2019-02-17 16:10:47 -05:00
f9873e9108 ItemFrame: fixed hardness 2019-02-17 16:10:21 -05:00
074baf7e1c Merge branch '3.5' into 3.6 2019-02-17 17:08:15 +00:00
2e0dd574e0 Set default health attribute value when changing max health, closes #2771 2019-02-17 17:06:32 +00:00
e16d8e31af Merge branch '3.5' into 3.6 2019-02-17 11:33:46 +00:00
3c93a57397 Entity: add a hack to prevent client-side movement when entity is not moving
this fixes #2227.
2019-02-17 11:33:34 +00:00
e2e927b328 3.6.3 is next 2019-02-14 17:17:18 +00:00
a8dab25201 Release 3.6.2 2019-02-14 17:15:19 +00:00
3de2b7969e Merge branch '3.5' into 3.6 2019-02-14 17:14:59 +00:00
8f486ea65d 3.5.13 is next 2019-02-14 17:14:34 +00:00
6b971b1761 Release 3.5.12 2019-02-14 16:55:44 +00:00
6f36fa504b TextFormat: make clean() less confusing, deduplicate some regex 2019-02-14 16:52:39 +00:00
8e73842a93 Player: work around 1.9 command casing crash bug, closes #2761 2019-02-14 16:17:17 +00:00
e71e18fc88 Merge branch '3.5' into 3.6 2019-02-14 16:02:30 +00:00
e1bacb5c6d Human: fixed hunger underflow when value is a fraction, closes #2761 2019-02-14 16:01:25 +00:00
44697e784a Sign: add validity checks on text encoding, scrub invalid UTF-8 on load
this works around a bug where corrupted text on preexisting signs can mess up the client. This also prevents corrupted text getting onto signs in the future by having them scrubbed and validated before applying them.
2019-02-14 15:16:51 +00:00
65529ff2ce Command: add @throws CommandException 2019-02-14 15:09:41 +00:00
c346c45d42 Use regex for command argument parsing, fixes #2266
moral of the story: don't abuse functions for things they weren't designed for... lol
2019-02-14 15:07:58 +00:00
c433fad0a7 another /u modifier 2019-02-14 13:50:56 +00:00
8fad5a6e30 TextFormat: use mb_scrub() in clean()
this redacts invalid characters to prevent them appearing in places that might break the client.
2019-02-13 20:01:36 +00:00
7a6f279825 TextFormat: fixed clean() not being unicode-aware 2019-02-13 17:06:44 +00:00
10b72c895d Merge branch '3.5' into 3.6 2019-02-13 14:53:00 +00:00
d520928888 Fixed startup time measurement, closes #2713, closes #2750 2019-02-13 14:50:52 +00:00
27767e7ddb Verify player name command input, closes #2729, closes #2749 2019-02-13 14:50:43 +00:00
243c12de7c EffectCommand: fix bounds check, closes #2055 2019-02-13 14:37:40 +00:00
372545e47e Merge branch '3.5' into 3.6 2019-02-12 16:56:53 +00:00
8913b48700 "Implement" info-update and reserved6 2019-02-12 16:56:12 +00:00
6ee4a0e090 Implement invisible bedrock to fix placement issues
I don't know why this wasn't done a long time ago, but here it is.
2019-02-12 16:41:23 +00:00
8bd8da4bc6 Merge branch '3.5' into 3.6 2019-02-10 17:15:48 +00:00
9ba4144a71 Server: remove useless gc_collect_cycles()
the server is shutting down at this point so who cares if there are cycles...
2019-02-10 17:15:41 +00:00
9da7c6af27 new metadata properties 2019-02-10 13:59:21 +00:00
109312284c 3.6.2 is next 2019-02-08 16:41:10 +00:00
51934614bc Release 3.6.1 2019-02-08 16:40:38 +00:00
9e89f65094 Fixed handling for some new blocks which slipped through the frontline 2019-02-08 16:38:17 +00:00
9562711b84 Updated BedrockData submodule to 1.9.0 2019-02-08 15:32:57 +00:00
30b49e0d22 Merge branch '3.5' into 3.6 2019-02-08 15:32:20 +00:00
a975868fc3 3.5.12 is next 2019-02-08 13:52:31 +00:00
b38b932845 Release 3.5.11 2019-02-08 13:52:09 +00:00
43cb19ebca Updated bedrockData submodule to 1.8.0 2019-02-08 13:51:41 +00:00
769cc91543 ItemFactory: fix crash when getting negative item IDs
these are now treated the same as any unknown item, and are now not possible to place.
2019-02-08 13:51:41 +00:00
278f37d3e0 3.6.1 is next 2019-02-07 22:07:31 +00:00
02a6ca84a9 Releass 3.6.0 2019-02-07 22:06:33 +00:00
b8703d5dff Protocol changes for 1.9.0 2019-02-07 21:56:42 +00:00
37c2d78731 3.5.11 is next 2019-02-07 20:47:05 +00:00
b7663e5815 Release 3.5.10 2019-02-07 20:46:27 +00:00
1d0ffa06f8 TallGrass: fixed placement on dirt, closes #2552 2019-02-07 16:55:25 +00:00
768cfe3953 Updated composer dependencies 2019-02-07 16:47:16 +00:00
2822465f33 Updated submodules 2019-02-07 16:20:45 +00:00
5da48f429f Fixed some remaining one-line field declarations, added type docs 2019-02-05 14:03:27 +00:00
dbd0d04549 fix wrong doc comments in Thread & Worker 2019-02-05 13:58:18 +00:00
0f92ec6d2a Level: Record a debug message when chunks are loaded without loaders 2019-02-03 16:24:10 +00:00
791b4d8ef3 SplashPotion: measure distance from eye height instead of base
this fixes effect durations being off (mostly), closes #2650
there are still some minor differences, but this is closer matching than the previous version.
2019-02-03 11:32:47 +00:00
0b7ff6f2e7 Level: properly mark some functions as @internal
this ensures these functions won't appear in the documentation.
2019-02-01 14:33:06 +00:00
af092b01e1 3.5.10 is next 2019-01-31 18:47:57 +00:00
d811217755 Release 3.5.9 2019-01-31 18:47:32 +00:00
d7f86f0240 Hack for client side regeneration (disable natural regeneration gamerule) (#2722) 2019-01-31 18:45:25 +00:00
5fe1d2e396 Level: fixed setChunk() bug introduced by eebd90ec42
Anyone who is using getChunkPlayers() should probably check that their code is actually doing what they think it's doing.
2019-01-31 18:28:42 +00:00
ddbb5363ef Block->getBreakTime() now throws InvalidArgumentException on items with bad efficiency values 2019-01-30 19:10:24 +00:00
d3704bfae4 3.5.9 is next 2019-01-30 12:49:46 +00:00
07f034d2da Release 3.5.8 2019-01-30 12:49:16 +00:00
331ae5498f Crash the generator when encountering invalid presets
this is a partial fix for #2717, but still not ideal because it'll spam whenever a chunk is attempted to be generated. However, fixing this properly requires potentially breaking API changes.
2019-01-30 12:29:04 +00:00
eebd90ec42 Level: fixed setChunk() leaking memory when not copying tiles/entities
closes #2719
2019-01-30 10:58:35 +00:00
8e47a40b4c Level: rename poorly-named parameter of setChunk()
this now has a mouthful of a name. I'd like to invert it, but I can't do that without silently breaking backwards compatibility, which is unacceptable.
2019-01-30 10:26:18 +00:00
0f9fdf6442 Fixed Cactus and Sugarcane growing through bad blocks
closes #2714
2019-01-29 17:32:48 +00:00
5364e4de68 Explosion: fix chest unpairing (root cause of #2701)
not ready to call this "fixed" yet because any chests that were already affected by the bug will still be affected. This change will prevent the creation of more broken chests like this.
2019-01-29 13:58:47 +00:00
1099e2044b SetupWizard: fixed user being able to avoid agreeing to license, closes #2703 2019-01-29 13:43:13 +00:00
80d48161d3 Crops: fixed consuming bonemeal on fully-grown crop, closes #2704 2019-01-29 13:41:02 +00:00
fbe8485696 Player: fixed falling causing starvation, closes #2708 2019-01-29 13:36:24 +00:00
a150f39b02 Fixed fall handling when flying, closes #2709 2019-01-29 13:28:43 +00:00
71d17c50d6 Fixed internal IP detection (#2711), closes #2702 2019-01-29 12:51:53 +00:00
3a18bdd6a0 Prevent block-picking unknown blocks 2019-01-28 17:45:48 +00:00
96857c65b6 Player: Fixed subscribing to broadcast permissions too early
Plugins would indirectly trigger permissible recalculation too early in the login sequence, which then caused their permissions to be recalculated and subscribing them to the broadcast permission far too early.
2019-01-27 15:08:08 +00:00
9826abd83e Stair: fixed crash if player is null 2019-01-26 16:59:03 +00:00
48c8c2a8c3 ItemFrame: address crashdump 2113950
thanks Mojang for designing this lovely system where so much undefined behaviour is able to take place :(
2019-01-26 16:58:25 +00:00
c83b7d9b69 Utils: fixed getNiceClosureName() not processing namespaced closures properly 2019-01-26 12:08:07 +00:00
c66dc7b273 Level: Optimize blockticking coordinate selection
generating 1 large bounded random costs the same as generating 4 small ones, so it makes more sense to do it like this instead.

Note that prior to 7.1 this code would not work due to it not handling 64-bit appropriately.
2019-01-25 19:43:16 +00:00
5bf7350ee5 Level: change some stupid var names 2019-01-25 17:10:44 +00:00
636cc1c199 Level: some mitigations for #2675
this reduces the performance impact by using smaller keys, which reduces the key distance for nearby blocks.
2019-01-25 13:17:33 +00:00
62b6405371 Use 2 space indents in YAML files
this is (mostly) a backport of #2593 to 3.5. Closes #2593.
2019-01-24 18:44:06 +00:00
9ecf23d3ee Fix some bugs in doxygen config 2019-01-24 11:26:48 +00:00
187f36fe38 ExperienceOrb: clean up some confusing code 2019-01-23 16:46:18 +00:00
dc89b48354 Human: Fixed XP drop amount to match vanilla
In vanilla it doesn't drop the exact number of points you collected. Rather, you lose a little for every level above 1 you had (1 level requires 7 points, later levels require +2 per level), and can recover at most 100 points. Hence, if you had 10 levels, you get back enough points to fill 5 levels and most of a 6th. 14-15 levels gets you the upper bound of about 7.5 levels.
2019-01-23 12:39:25 +00:00
e6d1c1dfbc Living: Get rid of delayed XP spawning, closes #2644
This is not identical to vanilla, but I don't care because it gets rid of edge cases and also makes it easier to integrate with EntityDeathEvent in the future.
2019-01-23 12:31:05 +00:00
5a8812b1dc 3.5.8 is next 2019-01-22 22:14:19 +00:00
4b8e4123af Release 3.5.7 2019-01-22 22:13:35 +00:00
45a4252c26 RCON: Explicitly specify connection backlog size, fixes #2685
I believe this is caused by a bug in the linux kernel, since it only impacts certain machines I tested (one, to be specific). Whatever the case, setting a max backlog size is prudent anyway, and fixes the problem.
2019-01-22 22:11:32 +00:00
feaaa925a7 Fixed a series of denial-of-service bugs in RCON
Packets with a too-short payload would either cause the RCON thread to hang until the client disconnected, or crash the RCON thread entirely.

commit 90bb1894d7f87645b806f5fc67d1b877bb963180
Author: Dylan K. Taylor <odigiman@gmail.com>
Date:   Tue Jan 22 18:15:46 2019 +0000

    fix some bugs in RCON
2019-01-22 22:05:15 +00:00
5221db1178 Updated BinaryUtils dependency 2019-01-22 17:22:11 +00:00
47321114eb login: allow 60 seconds clock drift on login JWT
can everyone please just use NTP already?
2019-01-20 15:51:29 +00:00
a27c9409f1 ExperienceOrb: fixed wrong condition 2019-01-20 12:30:24 +00:00
854f851525 ExperienceOrb: don't follow players in other worlds 2019-01-20 12:30:09 +00:00
9003b38be3 Fixed a wtf in Player->removeWindow() 2019-01-19 19:14:21 +00:00
a6a93f822f 3.5.7 is next 2019-01-19 17:48:16 +00:00
d4851a8f1f Release 3.5.6 2019-01-19 17:33:07 +00:00
480a513f30 Sign: fixed asserts on loading NBT, closes #2636 2019-01-19 16:42:22 +00:00
4fd3bee360 Entity: Address fireticks crashdumps
This will now throw an exception at the source instead of crashing when the entity is saved, which should put the blame on the correct plugin responsible for this.
This also includes magic method hacks to preserve backwards compatibility, since the fireTicks field is now protected.
2019-01-19 16:05:10 +00:00
41fd7545e3 RegionLoader: Account for unexpected EOF when reading chunks, closes #2676 2019-01-19 15:49:19 +00:00
82dddde159 Remove dead code in /time command handler 2019-01-19 15:15:04 +00:00
bc709efb77 Prevent stupidity with /enchant 2019-01-19 15:14:51 +00:00
cd98e6a23e Updated PreProcessor submodule 2019-01-19 13:57:17 +00:00
cb591a98f4 NetworkBinaryStream: Skip item NBT which is too large when encoded
Encoded tags larger than 32KB overflow the length field, so we can't send these over network. However, it's unreasonable to randomly throw this burden off onto users by crashing their servers, so the next best solution is to just not send the NBT. This is also not an ideal solution (books and the like with too-large tags won't work on the client side) but it's better than crashing the server or client due to a protocol bug. Mojang have confirmed this will be resolved by a future MCPE release, so we'll just work around this problem until then.
2019-01-16 21:50:43 +00:00
e9d1af0aee Fixed stack trace encoding bug in CrashDump
it was encoding the real (full) trace, instead of emitting the printable version.
2019-01-16 18:56:56 +00:00
0f545c410a Bow: Improve consistency with vanilla (JE at least), closes #2660
This change causes the existing test I did to now report ~55-60 blocks, which is much closer to parity with vanilla.
2019-01-15 14:42:41 +00:00
1c2ed0836f Sync composer dependencies 2019-01-15 10:26:33 +00:00
6cf30dc813 build 7.3 on travis 2019-01-11 23:54:44 +00:00
f7d9247d39 Server: Permit NULL value entries in pocketmine.yml worlds
this permits listing worlds to load as keys without values.
2019-01-10 19:30:37 +00:00
3380aa3ac2 Config: Assert only whitespace precedes .properties key, fixes #commented properties not being skipped 2019-01-07 12:25:05 +00:00
6a9cad8fb7 3.5.6 is next 2019-01-06 20:18:32 +00:00
169 changed files with 2914 additions and 1226 deletions

View File

@ -4,3 +4,7 @@ root = yes
[*]
indent_size = 4
indent_style = tab
[*.yml]
indent_size = 2
indent_style = space

8
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: pocketminemp
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom: https://github.com/pmmp/PocketMine-MP#donate

View File

@ -1,48 +0,0 @@
### Issue description
<!---
THIS ISSUE TRACKER IS FOR BUG REPORTING, NOT FOR HELP & SUPPORT. If you need help, use the links below.
- http://pmmp.readthedocs.io/en/rtfd/ - Documentation
- https://forums.pmmp.io - PMMP Forums
Any issues requesting updates to new versions of MCPE will be treated as spam.
Please do not create issues for missing/un-implemented gameplay features - they will be closed.
-->
<!--- Write a short description about the issue -->
<!--- If you are reporting a regression or unexpected behaviour, please include the below information: -->
- Expected result: What were you expecting to happen?
- Actual result: What actually happened?
### Steps to reproduce the issue
<!--- help us find the problem by adding steps to reproduce the issue -->
1. ...
2. ...
### OS and versions
<!--- use the 'version' command in PocketMine-MP
NOTE: LATEST is not a valid version. PocketMine-MP version should include Jenkins build number and/or git commit hash.
NO support whatsoever will be provided for third-party modified variants of PocketMine-MP. Issues relating to third-party modifications will be closed as spam.
Note that 32-bit platforms are no longer supported by PocketMine-MP and issues concerning 32-bit platforms will be closed.
-->
* PocketMine-MP: <!-- LATEST IS NOT A VALID VERSION -->
* PHP:
* Server OS:
* Game version: PE/Win10 (delete as appropriate)
### Plugins
- Test on a clean server without plugins: is the issue reproducible without any plugins loaded?
If the issue is **not** reproducible without plugins:
- Have you asked for help on our forums before creating an issue?
- Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section
- Paste your list of plugins here (use the 'plugins' command in PocketMine-MP)
### Crashdump, backtrace or other files
- Do not paste crashdumps into an issue - please use our Crash Archive at https://crash.pmmp.io for submitting crash reports to not spam the issue tracker. Add links to your reports in the Crash Archive here.
- Please use gist or anything else to add other files and add links here
* ...

View File

@ -0,0 +1,19 @@
---
name: API change request
about: Suggest a change, addition or removal to the plugin API
title: ''
labels: ''
assignees: ''
---
<!--- tell us what you want -->
## Description
<!--- explain why you want this and why it's a good idea -->
## Justification
<!--- (optional) describe alternative methods you've explored to achieve your goal -->
## Alternative methods

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,36 @@
---
name: Bug report
about: Unexpected non-crash behaviour (except missing gameplay features)
title: ''
labels: ''
assignees: ''
---
### Issue description
- Expected result: What were you expecting to happen?
- Actual result: What actually happened?
### Steps to reproduce the issue
1. ...
2. ...
### OS and versions
<!-- try the `version` command | LATEST IS NOT A VALID VERSION -->
* PocketMine-MP:
* PHP:
* Server OS:
* Game version: PE/Win10 (delete as appropriate)
### Plugins
<!--- use the `plugins` command and paste the output below -->
- If you remove all plugins, does the issue still occur?
- If the issue is **not** reproducible without plugins:
- Have you asked for help on our forums before creating an issue?
- Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section
### Crashdump, backtrace or other files
<!--- Submit crashdumps at https://crash.pmmp.io and paste a link -->
<!--- Use gist or anything else to add other files and add links here -->

15
.github/ISSUE_TEMPLATE/crash.md vendored Normal file
View File

@ -0,0 +1,15 @@
---
name: Crash
about: Report a crash in PocketMine-MP (not plugins)
title: Server crashed
labels: ''
assignees: ''
---
<!--- submit crashdump files to https://crash.pmmp.io -->
<!--- or, copy the data between ===BEGIN CRASH DUMP=== and ===END CRASH DUMP and paste it on a site like https://pastebin.com -->
Link to crashdump:
<!--- write additional information about the crash to help us find the problem -->
### Additional comments (optional)

View File

@ -0,0 +1,14 @@
---
name: Help & support
about: We don't accept support requests here. Try the links on the README.
title: ''
labels: Support request
assignees: ''
---
We don't accept support requests on the issue tracker. Please try the following links instead:
Documentation: http://pmmp.rtfd.io
Forums: https://forums.pmmp.io
Discord: https://discord.gg/bge7dYQ

View File

@ -0,0 +1,12 @@
---
name: Security/DoS vulnerability
about: 'Bug or exploit that can be used to attack servers (hint: don''t report it
on a public issue tracker)'
title: ''
labels: 'Auto: Spam'
assignees: ''
---
Please DO NOT report security vulnerabilities here.
Instead, send an email to team@pmmp.io or contact a developer directly, IN PRIVATE.

6
.github/support.yml vendored
View File

@ -5,7 +5,11 @@ supportLabel: "Support request"
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
supportComment: >
This issue tracker is not a support forum. Please use the [forums](https://forums.pmmp.io) for support.
Thanks, but this issue tracker not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bge7dYQ) | [Forums](https://forums.pmmp.io)
# Whether to close issues marked as support requests
close: true
# Whether to lock issues marked as support requests

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ server.properties
/pocketmine.yml
memory_dumps/*
resource_packs/
server.lock
# Common IDEs
.idea/

View File

@ -2,13 +2,14 @@ language: php
php:
- 7.2
- 7.3
before_script:
# - pecl install channel://pecl.php.net/pthreads-3.1.6
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
- echo | pecl install channel://pecl.php.net/yaml-2.0.4
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout c8cfacda84f21032d6014b53e72bf345ac901dac
- git checkout 6ca019c58b4fa09ee2ff490f2444e34bef0773d0
- phpize
- ./configure
- make

View File

@ -8,15 +8,20 @@ __A highly customisable, open source server software for Minecraft: Bedrock Edit
Head over to the [documentation site](http://pmmp.readthedocs.org/).
If you don't find what you're looking for there, [talk to a human](#discussion). Please do not use our issue tracker for support requests.
### Docker
We provide an official docker image on Docker Hub: [`pmmp/pocketmine-mp`](https://hub.docker.com/r/pmmp/pocketmine-mp).
### Discussion
- [Forums](https://forums.pmmp.io/)
- [Community Discord](https://discord.gg/bge7dYQ)
### Plugins
There are a very wide range of already-written plugins available which you can use to customise your server. Check out [Poggit](https://poggit.pmmp.io), or just search GitHub.
### For developers
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - A development tools plugin for creating plugins.
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
### Can I contribute?
Yes you can! Contributions are welcomed provided that they comply with our [Contributing Guidelines](CONTRIBUTING.md). Please ensure you read the relevant sections of the guidelines carefully before making a Pull Request or opening an Issue.
@ -27,6 +32,12 @@ Yes you can! Contributions are welcomed provided that they comply with our [Cont
**Note: Please avoid development builds unless there is no other alternative for what you need.** Development builds are subject to changes at any time without notice, and it is likely that your server or plugins might break without warning.
### Donate
Donations help support the development of the project and pay for our expenses.
- Bitcoin Cash (BCH): `qz9p8dqkv0r7aahdatu5uewqfkvstrglv58f8yle07`
- Bitcoin (BTC): `1PVAyDJ2g7kcjCxAC3C89oxpV2ZYcLad8T`
- [Patreon](https://www.patreon.com/pocketminemp)
## Licensing information
This program is free software: you can redistribute it and/or modify

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.1",
"pocketmine/nbt": "^0.2.6",
"pocketmine/math": "^0.2.0",
"pocketmine/snooze": "^0.1.0",
"daverandom/callback-validator": "dev-master",

40
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d3fb809caf4d5a5c99054f47f28ff271",
"content-hash": "2f5313e4ebd7b62c785cf683b27464b4",
"packages": [
{
"name": "adhocore/json-comment",
@ -92,16 +92,16 @@
},
{
"name": "pocketmine/binaryutils",
"version": "0.1.5",
"version": "0.1.8",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed"
"reference": "33f511715d22418c03368b49b45a6c25d6b33806"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/33f511715d22418c03368b49b45a6c25d6b33806",
"reference": "33f511715d22418c03368b49b45a6c25d6b33806",
"shasum": ""
},
"require": {
@ -119,10 +119,10 @@
],
"description": "Classes and methods for conveniently handling binary data",
"support": {
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.5",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.8",
"issues": "https://github.com/pmmp/BinaryUtils/issues"
},
"time": "2019-01-04T13:32:11+00:00"
"time": "2019-01-16T17:31:44+00:00"
},
{
"name": "pocketmine/math",
@ -160,16 +160,16 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.4",
"version": "0.2.7",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5"
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/05dddb51830fd8f3b6c93e553abe07643ec96fc5",
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
"shasum": ""
},
"require": {
@ -194,23 +194,23 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.4",
"source": "https://github.com/pmmp/NBT/tree/0.2.7",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-01-04T15:28:44+00:00"
"time": "2019-03-29T19:39:42+00:00"
},
{
"name": "pocketmine/raklib",
"version": "0.12.1",
"version": "0.12.4",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6"
"reference": "fc1ccc8e61b9033e5372436b2e28a7a95388373f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/334b469f2d0f070f17902d7ac584bea7c6149dc6",
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/fc1ccc8e61b9033e5372436b2e28a7a95388373f",
"reference": "fc1ccc8e61b9033e5372436b2e28a7a95388373f",
"shasum": ""
},
"require": {
@ -235,10 +235,10 @@
],
"description": "A RakNet server implementation written in PHP",
"support": {
"source": "https://github.com/pmmp/RakLib/tree/0.12.1",
"source": "https://github.com/pmmp/RakLib/tree/0.12.4",
"issues": "https://github.com/pmmp/RakLib/issues"
},
"time": "2019-01-04T14:23:37+00:00"
"time": "2019-05-02T14:53:51+00:00"
},
{
"name": "pocketmine/snooze",
@ -302,7 +302,7 @@
],
"description": "Standard library files required by PocketMine-MP and related projects",
"support": {
"source": "https://github.com/pmmp/SPL/tree/master"
"source": "https://github.com/pmmp/SPL/tree/0.3.2"
},
"time": "2018-08-12T15:17:39+00:00"
}

File diff suppressed because it is too large Load Diff

10
doxygen/index.md Normal file
View File

@ -0,0 +1,10 @@
## PocketMine-MP API Documentation
This site contains auto-generated API documentation for PocketMine-MP (and dependencies which are not outsourced). Documentation here is regenerated for every release.
This site can be accessed via https://apidoc.pmmp.io.
### Additional developer resources
- [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
- [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
- [DeveloperDocs](https://github.com/pmmp/DeveloperDocs/) - Reference, guides and specifications for the PocketMine-MP API

View File

@ -44,6 +44,7 @@ use function implode;
use function is_dir;
use function is_resource;
use function json_encode;
use function json_last_error_msg;
use function max;
use function mkdir;
use function ob_end_clean;
@ -146,7 +147,11 @@ class CrashDump{
$this->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
$this->addLine();
$this->addLine("===BEGIN CRASH DUMP===");
$this->encodedData = zlib_encode(json_encode($this->data, JSON_UNESCAPED_SLASHES), ZLIB_ENCODING_DEFLATE, 9);
$json = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if($json === false){
throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
}
$this->encodedData = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
foreach(str_split(base64_encode($this->encodedData), 76) as $line){
$this->addLine($line);
}
@ -238,6 +243,9 @@ class CrashDump{
}
if(isset($lastError)){
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$this->data["lastError"] = $lastError;
}

View File

@ -26,6 +26,7 @@ namespace pocketmine;
use pocketmine\block\Bed;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\block\UnknownBlock;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\entity\Effect;
@ -103,6 +104,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket;
@ -185,6 +187,7 @@ use function min;
use function preg_match;
use function round;
use function spl_object_hash;
use function sqrt;
use function strlen;
use function strpos;
use function strtolower;
@ -390,7 +393,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function isBanned() : bool{
return $this->server->getNameBans()->isBanned($this->iusername);
return $this->server->getNameBans()->isBanned($this->username);
}
public function setBanned(bool $value){
@ -403,14 +406,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function isWhitelisted() : bool{
return $this->server->isWhitelisted($this->iusername);
return $this->server->isWhitelisted($this->username);
}
public function setWhitelisted(bool $value){
if($value){
$this->server->addWhitelist($this->iusername);
$this->server->addWhitelist($this->username);
}else{
$this->server->removeWhitelist($this->iusername);
$this->server->removeWhitelist($this->username);
}
}
@ -475,8 +478,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function setFlying(bool $value){
$this->flying = $value;
$this->sendSettings();
if($this->flying !== $value){
$this->flying = $value;
$this->resetFallDistance();
$this->sendSettings();
}
}
public function isFlying() : bool{
@ -688,14 +694,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->perm->recalculatePermissions();
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
if($this->spawned){
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
}
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
}
$this->sendCommandData();
}
}
@ -715,7 +721,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$data = new CommandData();
$data->commandName = $command->getName();
//TODO: commands containing uppercase letters in the name crash 1.9.0 client
$data->commandName = strtolower($command->getName());
$data->commandDescription = $this->server->getLanguage()->translateString($command->getDescription());
$data->flags = 0;
$data->permission = 0;
@ -1058,6 +1065,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return; //avoid player spawning twice (this can only happen on 3.x with a custom malicious client)
}
$this->spawned = true;
$this->setImmobile(false);
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
@ -1076,10 +1084,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->server->broadcastMessage($ev->getJoinMessage());
}
$this->setImmobile(false);
$this->noDamageTicks = 60;
foreach($this->usedChunks as $index => $c){
foreach($this->usedChunks as $index => $hasSent){
if(!$hasSent){
continue; //this will happen when the chunk is ready to send
}
Level::getXZ($index, $chunkX, $chunkZ);
foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){
if($entity !== $this and !$entity->isClosed() and $entity->isAlive() and !$entity->isFlaggedForDespawn()){
@ -1409,19 +1419,17 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->allowFlight = $this->isCreative();
if($this->isSpectator()){
$this->flying = true;
$this->setFlying(true);
$this->keepMovement = true;
$this->despawnFromAll();
}else{
$this->keepMovement = $this->allowMovementCheats;
if($this->isSurvival()){
$this->flying = false;
$this->setFlying(false);
}
$this->spawnToAll();
}
$this->resetFallDistance();
$this->namedtag->setInt("playerGameType", $this->gamemode);
if(!$client){ //Gamemode changed by server, do not send for client changes
$this->sendGamemode();
@ -1647,7 +1655,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}else{
$this->broadcastMovement();
$distance = $from->distance($to);
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
@ -1678,6 +1686,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->newPosition = null;
}
public function fall(float $fallDistance) : void{
if(!$this->flying){
parent::fall($fallDistance);
}
}
public function jump() : void{
(new PlayerJumpEvent($this))->call();
parent::jump();
@ -1920,7 +1934,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
if(!$this->server->isWhitelisted($this->iusername) and $this->kick("Server is white-listed", false)){
if(!$this->server->isWhitelisted($this->username) and $this->kick("Server is white-listed", false)){
return true;
}
@ -2050,7 +2064,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
$manager = $this->server->getResourcePackManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById(substr($uuid, 0, strpos($uuid, "_"))); //dirty hack for mojang's dirty hack for versions
//dirty hack for mojang's dirty hack for versions
$splitPos = strpos($uuid, "_");
if($splitPos !== false){
$uuid = substr($uuid, 0, $splitPos);
}
$pack = $manager->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->close("", "disconnectionScreen.resourcePack", true);
@ -2090,6 +2110,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var float[] $pos */
$pos = $this->namedtag->getListTag("Pos")->getAllValues();
$this->level->registerChunkLoader($this, ((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4, true);
$this->usedChunks[Level::chunkHash(((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4)] = false;
parent::__construct($this->level, $this->namedtag);
$ev = new PlayerLoginEvent($this, "Plugin reason");
@ -2137,6 +2158,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->dataPacket($pk);
$this->sendDataPacket(new AvailableEntityIdentifiersPacket());
$this->sendDataPacket(new BiomeDefinitionListPacket());
$this->level->sendTime($this);
@ -2714,6 +2736,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
$block = $this->level->getBlockAt($packet->blockX, $packet->blockY, $packet->blockZ);
if($block instanceof UnknownBlock){
return true;
}
$item = $block->getPickedItem();
if($packet->addUserData){
@ -2938,8 +2963,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$ev->call();
if($ev->isCancelled()){
$this->sendSettings();
}else{
}else{ //don't use setFlying() here, to avoid feedback loops
$this->flying = $ev->isFlying();
$this->resetFallDistance();
}
$handled = true;
@ -2969,7 +2995,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$t = $this->level->getTile($pos);
if($t instanceof Spawnable){
$nbt = new NetworkLittleEndianNBTStream();
$compound = $nbt->read($packet->namedtag);
$_ = 0;
$compound = $nbt->read($packet->namedtag, false, $_, 512);
if(!($compound instanceof CompoundTag)){
throw new \InvalidArgumentException("Expected " . CompoundTag::class . " in block entity NBT, got " . (is_object($compound) ? get_class($compound) : gettype($compound)));
@ -3285,7 +3312,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
/**
* Resets the title duration settings.
* Resets the title duration settings to defaults and removes any existing titles.
*/
public function resetTitles(){
$pk = new SetTitlePacket();
@ -3613,7 +3640,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
if($this->inventory !== null){
$this->inventory->setHeldItemIndex(0, false); //This is already handled when sending contents, don't send it twice
$this->inventory->setHeldItemIndex(0);
$this->inventory->clearAll();
}
if($this->armorInventory !== null){
@ -3621,8 +3648,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
//TODO: allow this number to be manipulated during PlayerDeathEvent
$this->level->dropExperience($this, $this->getXpDropAmount());
$this->setXpAndProgress(0, 0);
if($ev->getDeathMessage() != ""){
$this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS);
$this->server->broadcastMessage($ev->getDeathMessage());
}
}
@ -3881,17 +3912,17 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @param Inventory $inventory
* @param bool $force Forces removal of permanent windows such as normal inventory, cursor
*
* @throws \BadMethodCallException if trying to remove a fixed inventory window without the `force` parameter as true
* @throws \InvalidArgumentException if trying to remove a fixed inventory window without the `force` parameter as true
*/
public function removeWindow(Inventory $inventory, bool $force = false){
$id = $this->windows[$hash = spl_object_hash($inventory)] ?? null;
if($id !== null and !$force and isset($this->permanentWindows[$id])){
throw new \BadMethodCallException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
throw new \InvalidArgumentException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
}
$inventory->close($this);
if($id !== null){
$inventory->close($this);
unset($this->windows[$hash], $this->windowIndex[$id], $this->permanentWindows[$id]);
}
}

View File

@ -36,10 +36,7 @@ namespace pocketmine {
use pocketmine\utils\VersionString;
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.5.5";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;
require_once __DIR__ . '/VersionInfo.php';
const MIN_PHP_VERSION = "7.2.0";
@ -177,11 +174,10 @@ namespace pocketmine {
ini_set("default_charset", "utf-8");
ini_set("memory_limit", '-1');
define('pocketmine\START_TIME', microtime(true));
define('pocketmine\RESOURCE_PATH', \pocketmine\PATH . 'src' . DIRECTORY_SEPARATOR . 'pocketmine' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR);
$opts = getopt("", ["data:", "plugins:", "no-wizard"]);
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR);
define('pocketmine\PLUGIN_PATH', isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR);
@ -190,9 +186,25 @@ namespace pocketmine {
mkdir(\pocketmine\DATA, 0777, true);
}
define('pocketmine\LOCK_FILE_PATH', \pocketmine\DATA . 'server.lock');
define('pocketmine\LOCK_FILE', fopen(\pocketmine\LOCK_FILE_PATH, "cb"));
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
critical_error("Another " . \pocketmine\NAME . " instance is already using this folder (" . realpath(\pocketmine\DATA) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
//Logger has a dependency on timezone
$tzError = Timezone::init();
if(isset($opts["enable-ansi"])){
Terminal::init(true);
}elseif(isset($opts["disable-ansi"])){
Terminal::init(false);
}else{
Terminal::init();
}
$logger = new MainLogger(\pocketmine\DATA . "server.log");
$logger->registerStatic();
@ -204,6 +216,9 @@ namespace pocketmine {
if(extension_loaded("xdebug")){
$logger->warning(PHP_EOL . PHP_EOL . PHP_EOL . "\tYou are running " . \pocketmine\NAME . " with xdebug enabled. This has a major impact on performance." . PHP_EOL . PHP_EOL);
}
if(!extension_loaded("pocketmine_chunkutils")){
$logger->warning("ChunkUtils extension is missing. Anvil-format worlds will experience degraded performance.");
}
if(\Phar::running(true) === ""){
$logger->warning("Non-packaged " . \pocketmine\NAME . " installation detected. Consider using a phar in production for better performance.");
@ -245,6 +260,8 @@ namespace pocketmine {
}
}
//TODO: move this to a Server field
define('pocketmine\START_TIME', microtime(true));
ThreadManager::init();
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);

View File

@ -112,7 +112,6 @@ use function array_sum;
use function asort;
use function assert;
use function base64_encode;
use function bin2hex;
use function class_exists;
use function count;
use function define;
@ -122,9 +121,7 @@ use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function floor;
use function function_exists;
use function gc_collect_cycles;
use function get_class;
use function getmypid;
use function getopt;
@ -143,6 +140,7 @@ use function max;
use function microtime;
use function min;
use function mkdir;
use function ob_end_flush;
use function pcntl_signal;
use function pcntl_signal_dispatch;
use function preg_replace;
@ -295,15 +293,6 @@ class Server{
/** @var int */
public $networkCompressionLevel = 7;
/** @var bool */
private $autoTickRate = true;
/** @var int */
private $autoTickRateLimit = 20;
/** @var bool */
private $alwaysTickPlayers = false;
/** @var int */
private $baseTickRate = 1;
/** @var int */
private $autoSaveTicker = 0;
/** @var int */
@ -809,6 +798,7 @@ class Server{
* @return bool
*/
public function hasOfflinePlayerData(string $name) : bool{
$name = strtolower($name);
return file_exists($this->getDataPath() . "players/$name.dat");
}
@ -1056,7 +1046,7 @@ class Server{
*/
public function unloadLevel(Level $level, bool $forceUnload = false) : bool{
if($level === $this->getDefaultLevel() and !$forceUnload){
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
throw new \InvalidStateException("The default world cannot be unloaded while running, please switch worlds.");
}
return $level->unload($forceUnload);
@ -1082,7 +1072,7 @@ class Server{
*/
public function loadLevel(string $name) : bool{
if(trim($name) === ""){
throw new LevelException("Invalid empty level name");
throw new LevelException("Invalid empty world name");
}
if($this->isLevelLoaded($name)){
return true;
@ -1101,16 +1091,24 @@ class Server{
return false;
}
/**
* @var LevelProvider $provider
* @see LevelProvider::__construct()
*/
$provider = new $providerClass($path);
try{
GeneratorManager::getGenerator($provider->getGenerator(), true);
}catch(\InvalidArgumentException $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Unknown generator \"" . $provider->getGenerator() . "\""]));
return false;
}
/** @see LevelProvider::__construct() */
$level = new Level($this, $name, new $providerClass($path));
$level = new Level($this, $name, $provider);
$this->levels[$level->getId()] = $level;
(new LevelLoadEvent($level))->call();
$level->setTickRate($this->baseTickRate);
return true;
}
@ -1142,7 +1140,7 @@ 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");
throw new \InvalidStateException("Default world provider has not been registered");
}
}
@ -1154,8 +1152,6 @@ class Server{
$level = new Level($this, $name, new $providerClass($path));
$this->levels[$level->getId()] = $level;
$level->setTickRate($this->baseTickRate);
(new LevelInitEvent($level))->call();
(new LevelLoadEvent($level))->call();
@ -1593,11 +1589,6 @@ class Server{
}
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
$this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true);
$this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20);
$this->alwaysTickPlayers = (bool) $this->getProperty("level-settings.always-tick-players", false);
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
@ -1696,12 +1687,17 @@ class Server{
Entity::init();
Tile::init();
BlockFactory::init();
BlockFactory::registerStaticRuntimeIdMappings();
Enchantment::init();
ItemFactory::init();
Item::initCreativeItems();
Biome::init();
LevelProviderManager::init();
if(extension_loaded("leveldb")){
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable"));
}
GeneratorManager::registerDefaultGenerators();
$this->craftingManager = new CraftingManager();
$this->resourceManager = new ResourcePackManager($this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR, $this->logger);
@ -1723,15 +1719,10 @@ class Server{
$this->network->registerInterface(new RakLibInterface($this));
LevelProviderManager::init();
if(extension_loaded("leveldb")){
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable"));
}
GeneratorManager::registerDefaultGenerators();
foreach((array) $this->getProperty("worlds", []) as $name => $options){
if(!is_array($options)){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->loadLevel($name)){
@ -2036,7 +2027,7 @@ class Server{
}
public function reload(){
$this->logger->info("Saving levels...");
$this->logger->info("Saving worlds...");
foreach($this->levels as $level){
$level->save();
@ -2114,7 +2105,7 @@ class Server{
$player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed"));
}
$this->getLogger()->debug("Unloading all levels");
$this->getLogger()->debug("Unloading all worlds");
foreach($this->getLevels() as $level){
$this->unloadLevel($level, true);
}
@ -2146,9 +2137,6 @@ class Server{
$this->network->unregisterInterface($interface);
}
}
$this->getLogger()->debug("Collecting cycles");
gc_collect_cycles();
}catch(\Throwable $e){
$this->logger->logException($e);
$this->logger->emergency("Crashed while crashing, killing process");
@ -2219,6 +2207,7 @@ class Server{
* @param array|null $trace
*/
public function exceptionHandler(\Throwable $e, $trace = null){
while(@ob_end_flush()){}
global $lastError;
if($trace === null){
@ -2250,6 +2239,7 @@ class Server{
}
public function crashDump(){
while(@ob_end_flush()){}
if(!$this->isRunning){
return;
}
@ -2281,6 +2271,7 @@ class Server{
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
$report = false;
}
}
@ -2430,8 +2421,6 @@ class Server{
foreach($this->players as $p){
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers and $p->spawned){
$p->onUpdate($currentTick);
}
}
@ -2441,32 +2430,13 @@ class Server{
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
continue;
}
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
continue;
}
$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();
}
if($tickMs >= 50){
$this->getLogger()->debug(sprintf("World \"%s\" took too long to tick: %gms (%g ticks)", $level->getName(), $tickMs, round($tickMs / 50, 2)));
}
}
}
@ -2557,7 +2527,7 @@ class Server{
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));
$this->logger->debug("Unhandled raw packet from $address $port: " . base64_encode($payload));
}
}catch(\Throwable $e){
$this->logger->logException($e);
@ -2619,7 +2589,11 @@ class Server{
if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){
$this->autoSaveTicker = 0;
$this->getLogger()->debug("[Auto Save] Saving worlds...");
$start = microtime(true);
$this->doAutoSave();
$time = (microtime(true) - $start);
$this->getLogger()->debug("[Auto Save] Save completed in " . ($time >= 1 ? round($time, 3) . "s" : round($time * 1000) . "ms"));
}
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){

View File

@ -28,7 +28,7 @@ namespace pocketmine;
*/
abstract class Thread extends \Thread{
/** @var \ClassLoader */
/** @var \ClassLoader|null */
protected $classLoader;
/** @var string|null */
protected $composerAutoloaderPath;

View File

@ -0,0 +1,27 @@
<?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/
*
*
*/
namespace pocketmine;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.8.4";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -28,7 +28,7 @@ namespace pocketmine;
*/
abstract class Worker extends \Worker{
/** @var \ClassLoader */
/** @var \ClassLoader|null */
protected $classLoader;
/** @var string|null */
protected $composerAutoloaderPath;

View File

@ -36,9 +36,11 @@ use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\metadata\Metadatable;
use pocketmine\metadata\MetadataValue;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use function array_merge;
use function get_class;
use const PHP_INT_MAX;
class Block extends Position implements BlockIds, Metadatable{
@ -116,7 +118,7 @@ class Block extends Position implements BlockIds, Metadatable{
* @return int
*/
public function getRuntimeId() : int{
return BlockFactory::toStaticRuntimeId($this->getId(), $this->getDamage());
return RuntimeBlockMapping::toStaticRuntimeId($this->getId(), $this->getDamage());
}
/**
@ -268,6 +270,7 @@ class Block extends Position implements BlockIds, Metadatable{
* @param Item $item
*
* @return float
* @throws \InvalidArgumentException if the item efficiency is not a positive number
*/
public function getBreakTime(Item $item) : float{
$base = $this->getHardness();
@ -279,7 +282,7 @@ class Block extends Position implements BlockIds, Metadatable{
$efficiency = $item->getMiningEfficiency($this);
if($efficiency <= 0){
throw new \RuntimeException("Item efficiency is invalid");
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
}
$base /= $efficiency;

View File

@ -25,9 +25,7 @@ namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\level\Position;
use function file_get_contents;
use function json_decode;
use function max;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use function min;
/**
@ -52,15 +50,6 @@ class BlockFactory{
/** @var \SplFixedArray<float> */
public static $blastResistance = null;
/** @var int[] */
public static $staticRuntimeIdMap = [];
/** @var int[] */
public static $legacyIdMap = [];
/** @var int */
private static $lastRuntimeId = 0;
/**
* Initializes the block factory. By default this is called only once on server start, however you may wish to use
* this if you need to reset the block factory back to its original defaults for whatever reason.
@ -171,7 +160,7 @@ class BlockFactory{
self::registerBlock(new Cake());
//TODO: REPEATER_BLOCK
//TODO: POWERED_REPEATER
//TODO: INVISIBLEBEDROCK
self::registerBlock(new InvisibleBedrock());
self::registerBlock(new Trapdoor());
//TODO: MONSTER_EGG
self::registerBlock(new StoneBricks());
@ -319,13 +308,13 @@ class BlockFactory{
self::registerBlock(new Stonecutter());
self::registerBlock(new GlowingObsidian());
self::registerBlock(new NetherReactor());
//TODO: INFO_UPDATE
//TODO: INFO_UPDATE2
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE, 0, "update!"));
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE2, 0, "ate!upd"));
//TODO: MOVINGBLOCK
//TODO: OBSERVER
//TODO: STRUCTURE_BLOCK
//TODO: RESERVED6
self::registerBlock(new Reserved6(Block::RESERVED6, 0, "reserved6"));
for($id = 0, $size = self::$fullList->getSize() >> 4; $id < $size; ++$id){
if(self::$fullList[$id << 4] === null){
@ -427,16 +416,9 @@ class BlockFactory{
return $b !== null and !($b instanceof UnknownBlock);
}
public static function registerStaticRuntimeIdMappings() : void{
/** @var mixed[] $runtimeIdMap */
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
foreach($runtimeIdMap as $k => $obj){
self::registerMapping($k, $obj["id"], $obj["data"]);
}
}
/**
* @internal
* @deprecated
*
* @param int $id
* @param int $meta
@ -444,15 +426,11 @@ class BlockFactory{
* @return int
*/
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
/*
* 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];
return RuntimeBlockMapping::toStaticRuntimeId($id, $meta);
}
/**
* @deprecated
* @internal
*
* @param int $runtimeId
@ -460,13 +438,6 @@ class BlockFactory{
* @return int[] [id, meta]
*/
public static function fromStaticRuntimeId(int $runtimeId) : array{
$v = self::$legacyIdMap[$runtimeId];
return [$v >> 4, $v & 0xf];
}
private static function registerMapping(int $staticRuntimeId, int $legacyId, int $legacyMeta) : void{
self::$staticRuntimeIdMap[($legacyId << 4) | $legacyMeta] = $staticRuntimeId;
self::$legacyIdMap[$staticRuntimeId] = ($legacyId << 4) | $legacyMeta;
self::$lastRuntimeId = max(self::$lastRuntimeId, $staticRuntimeId);
return RuntimeBlockMapping::fromStaticRuntimeId($runtimeId);
}
}

View File

@ -96,9 +96,12 @@ class Cactus extends Transparent{
if($b->getId() === self::AIR){
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
if($ev->isCancelled()){
break;
}
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
}else{
break;
}
}
$this->meta = 0;

View File

@ -46,7 +46,7 @@ class Carpet extends Flowable{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Carpet";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Carpet";
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -30,6 +30,18 @@ use pocketmine\math\Vector3;
class CobblestoneWall extends Transparent{
public const NONE_MOSSY_WALL = 0;
public const MOSSY_WALL = 1;
public const GRANITE_WALL = 2;
public const DIORITE_WALL = 3;
public const ANDESITE_WALL = 4;
public const SANDSTONE_WALL = 5;
public const BRICK_WALL = 6;
public const STONE_BRICK_WALL = 7;
public const MOSSY_STONE_BRICK_WALL = 8;
public const NETHER_BRICK_WALL = 9;
public const END_STONE_BRICK_WALL = 10;
public const PRISMARINE_WALL = 11;
public const RED_SANDSTONE_WALL = 12;
public const RED_NETHER_BRICK_WALL = 13;
protected $id = self::COBBLESTONE_WALL;
@ -50,11 +62,23 @@ class CobblestoneWall extends Transparent{
}
public function getName() : string{
if($this->meta === 0x01){
return "Mossy Cobblestone Wall";
}
return "Cobblestone Wall";
static $names = [
self::NONE_MOSSY_WALL => "Cobblestone",
self::MOSSY_WALL => "Mossy Cobblestone",
self::GRANITE_WALL => "Granite",
self::DIORITE_WALL => "Diorite",
self::ANDESITE_WALL => "Andesite",
self::SANDSTONE_WALL => "Sandstone",
self::BRICK_WALL => "Brick",
self::STONE_BRICK_WALL => "Stone Brick",
self::MOSSY_STONE_BRICK_WALL => "Mossy Stone Brick",
self::NETHER_BRICK_WALL => "Nether Brick",
self::END_STONE_BRICK_WALL => "End Stone Brick",
self::PRISMARINE_WALL => "Prismarine",
self::RED_SANDSTONE_WALL => "Red Sandstone",
self::RED_NETHER_BRICK_WALL => "Red Nether Brick"
];
return ($names[$this->getVariant()] ?? "Unknown") . " Wall";
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -35,7 +35,7 @@ class Concrete extends Solid{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete";
}
public function getHardness() : float{

View File

@ -34,7 +34,7 @@ class ConcretePowder extends Fallable{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete Powder";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete Powder";
}
public function getHardness() : float{

View File

@ -43,7 +43,7 @@ abstract class Crops extends Flowable{
public function onActivate(Item $item, Player $player = null) : bool{
if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
if($this->meta < 7 and $item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
$block = clone $this;
$block->meta += mt_rand(2, 5);
if($block->meta > 7){

View File

@ -39,7 +39,7 @@ class DoublePlant extends Flowable{
}
public function canBeReplaced() : bool{
return $this->meta === 2 or $this->meta === 3; //grass or fern
return $this->getVariant() === 2 or $this->getVariant() === 3; //grass or fern
}
public function getName() : string{
@ -95,11 +95,11 @@ class DoublePlant extends Flowable{
}
public function getToolType() : int{
return ($this->meta === 2 or $this->meta === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
}
public function getToolHarvestLevel() : int{
return ($this->meta === 2 or $this->meta === 3) ? 1 : 0; //only grass or fern require shears
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? 1 : 0; //only grass or fern require shears
}
public function getDrops(Item $item) : array{

View File

@ -56,7 +56,7 @@ class Flower extends Flowable{
self::TYPE_PINK_TULIP => "Pink Tulip",
self::TYPE_OXEYE_DAISY => "Oxeye Daisy"
];
return $names[$this->meta] ?? "Unknown";
return $names[$this->getVariant()] ?? "Unknown";
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{

View File

@ -0,0 +1,31 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
class InfoUpdate extends Solid{
public function getHardness() : float{
return 1;
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
class InvisibleBedrock extends Transparent{
protected $id = self::INVISIBLE_BEDROCK;
public function __construct(){
}
public function getName() : string{
return "Invisible Bedrock";
}
public function getHardness() : float{
return -1;
}
public function getBlastResistance() : float{
return 18000000;
}
public function isBreakable(Item $item) : bool{
return false;
}
}

View File

@ -44,4 +44,8 @@ class IronTrapdoor extends Trapdoor{
public function getToolHarvestLevel() : int{
return TieredTool::TIER_WOODEN;
}
public function getFuelTime() : int{
return 0; //TODO: remove this hack on 4.0
}
}

View File

@ -65,7 +65,7 @@ class ItemFrame extends Flowable{
2 => Vector3::SIDE_NORTH,
3 => Vector3::SIDE_SOUTH
];
if(!$this->getSide($sides[$this->meta])->isSolid()){
if(isset($sides[$this->meta]) and !$this->getSide($sides[$this->meta])->isSolid()){
$this->level->useBreakOn($this);
}
}
@ -112,4 +112,8 @@ class ItemFrame extends Flowable{
public function isAffectedBySilkTouch() : bool{
return false;
}
public function getHardness() : float{
return 0.25;
}
}

View File

@ -58,8 +58,10 @@ class Ladder extends Transparent{
}
public function onEntityCollide(Entity $entity) : void{
$entity->resetFallDistance();
$entity->onGround = true;
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{

View File

@ -188,7 +188,7 @@ class Leaves extends Transparent{
}
public function canDropApples() : bool{
return $this->meta === self::OAK;
return $this->getVariant() === self::OAK;
}
public function getFlameEncouragement() : int{

View File

@ -44,6 +44,6 @@ class Leaves2 extends Leaves{
}
public function canDropApples() : bool{
return $this->meta === self::DARK_OAK;
return $this->getVariant() === self::DARK_OAK;
}
}

View File

@ -46,4 +46,6 @@ class NoteBlock extends Solid{
public function getToolType() : int{
return BlockToolType::TYPE_AXE;
}
//TODO
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
class PackedIce extends Solid{
protected $id = self::PACKED_ICE;
@ -46,4 +48,8 @@ class PackedIce extends Solid{
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
}

View File

@ -55,7 +55,7 @@ class Quartz extends Solid{
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
if($this->meta !== self::NORMAL){
if($this->getVariant() !== self::NORMAL){
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
}
return $this->getLevel()->setBlock($blockReplace, $this, true, true);

View File

@ -0,0 +1,31 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
class Reserved6 extends Solid{
public function getHardness() : float{
return 0;
}
}

View File

@ -40,7 +40,7 @@ class Sand extends Fallable{
}
public function getName() : string{
if($this->meta === 0x01){
if($this->getVariant() === 0x01){
return "Red Sand";
}

View File

@ -78,7 +78,7 @@ abstract class Slab extends Transparent{
}
}else{ //TODO: collision
if($blockReplace->getId() === $this->id){
if($blockReplace->getVariant() === $this->meta){
if($blockReplace->getVariant() === $this->getVariant()){
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
return true;

View File

@ -30,6 +30,6 @@ class StainedClay extends HardenedClay{
protected $id = self::STAINED_CLAY;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Clay";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Clay";
}
}

View File

@ -30,6 +30,6 @@ class StainedGlass extends Glass{
protected $id = self::STAINED_GLASS;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass";
}
}

View File

@ -30,6 +30,6 @@ class StainedGlassPane extends GlassPane{
protected $id = self::STAINED_GLASS_PANE;
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass Pane";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass Pane";
}
}

View File

@ -89,7 +89,7 @@ abstract class Stair extends Transparent{
2 => 1,
3 => 3
];
$this->meta = $faces[$player->getDirection()] & 0x03;
$this->meta = $player !== null ? $faces[$player->getDirection()] & 0x03 : 0;
if(($clickVector->y > 0.5 and $face !== Vector3::SIDE_UP) or $face === Vector3::SIDE_DOWN){
$this->meta |= 0x04; //Upside-down stairs
}

View File

@ -26,6 +26,12 @@ namespace pocketmine\block;
class StoneSlab2 extends StoneSlab{
public const TYPE_RED_SANDSTONE = 0;
public const TYPE_PURPUR = 1;
public const TYPE_PRISMARINE = 2;
public const TYPE_DARK_PRISMARINE = 3;
public const TYPE_PRISMARINE_BRICKS = 4;
public const TYPE_MOSSY_COBBLESTONE = 5;
public const TYPE_SMOOTH_SANDSTONE = 6;
public const TYPE_RED_NETHER_BRICK = 7;
protected $id = self::STONE_SLAB2;
@ -36,7 +42,13 @@ class StoneSlab2 extends StoneSlab{
public function getName() : string{
static $names = [
self::TYPE_RED_SANDSTONE => "Red Sandstone",
self::TYPE_PURPUR => "Purpur"
self::TYPE_PURPUR => "Purpur",
self::TYPE_PRISMARINE => "Prismarine",
self::TYPE_DARK_PRISMARINE => "Dark Prismarine",
self::TYPE_PRISMARINE_BRICKS => "Prismarine Bricks",
self::TYPE_MOSSY_COBBLESTONE => "Mossy Cobblestone",
self::TYPE_SMOOTH_SANDSTONE => "Smooth Sandstone",
self::TYPE_RED_NETHER_BRICK => "Red Nether Brick"
];
return (($this->meta & 0x08) > 0 ? "Upper " : "") . ($names[$this->getVariant()] ?? "") . " Slab";

View File

@ -37,6 +37,10 @@ class Stonecutter extends Solid{
return "Stonecutter";
}
public function getHardness() : float{
return 3.5;
}
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}

View File

@ -50,9 +50,11 @@ class Sugarcane extends Flowable{
if($b->getId() === self::AIR){
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
$ev->call();
if(!$ev->isCancelled()){
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
if($ev->isCancelled()){
break;
}
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
}else{
break;
}
}

View File

@ -25,6 +25,8 @@ namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\FlintSteel;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
@ -51,8 +53,10 @@ class TNT extends Solid{
}
public function onActivate(Item $item, Player $player = null) : bool{
if($item instanceof FlintSteel){
$item->applyDamage(1);
if($item instanceof FlintSteel or $item->hasEnchantment(Enchantment::FIRE_ASPECT)){
if($item instanceof Durable){
$item->applyDamage(1);
}
$this->ignite();
return true;
}

View File

@ -51,8 +51,8 @@ class TallGrass extends Flowable{
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
$down = $this->getSide(Vector3::SIDE_DOWN);
if($down->getId() === self::GRASS){
$down = $this->getSide(Vector3::SIDE_DOWN)->getId();
if($down === self::GRASS or $down === self::DIRT){
$this->getLevel()->setBlock($blockReplace, $this, true);
return true;

View File

@ -67,7 +67,7 @@ class Water extends Liquid{
public function onEntityCollide(Entity $entity) : void{
$entity->resetFallDistance();
if($entity->fireTicks > 0){
if($entity->isOnFire()){
$entity->extinguish();
}

View File

@ -43,7 +43,7 @@ class Wool extends Solid{
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Wool";
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Wool";
}
public function getBreakTime(Item $item) : float{

View File

@ -26,6 +26,7 @@ declare(strict_types=1);
*/
namespace pocketmine\command;
use pocketmine\command\utils\CommandException;
use pocketmine\lang\TextContainer;
use pocketmine\lang\TranslationContainer;
use pocketmine\permission\PermissionManager;
@ -92,6 +93,7 @@ abstract class Command{
* @param string[] $args
*
* @return mixed
* @throws CommandException
*/
abstract public function execute(CommandSender $sender, string $commandLabel, array $args);

View File

@ -66,13 +66,13 @@ use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\Server;
use function array_map;
use function array_shift;
use function count;
use function explode;
use function implode;
use function min;
use function str_getcsv;
use function preg_match_all;
use function stripslashes;
use function strpos;
use function strtolower;
use function trim;
@ -247,7 +247,16 @@ class SimpleCommandMap implements CommandMap{
}
public function dispatch(CommandSender $sender, string $commandLine) : bool{
$args = array_map("\stripslashes", str_getcsv($commandLine, " "));
$args = [];
preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches);
foreach($matches[0] as $k => $_){
for($i = 1; $i <= 2; ++$i){
if($matches[$i][$k] !== ""){
$args[$k] = stripslashes($matches[$i][$k]);
break;
}
}
}
$sentCommandLabel = "";
$target = $this->matchCommand($sentCommandLabel, $args);

View File

@ -53,6 +53,9 @@ class DeopCommand extends VanillaCommand{
}
$name = array_shift($args);
if(!Player::isValidUserName($name)){
throw new InvalidCommandSyntaxException();
}
$player = $sender->getServer()->getOfflinePlayer($name);
$player->setOp(false);

View File

@ -83,7 +83,7 @@ class EffectCommand extends VanillaCommand{
$amplification = 0;
if(count($args) >= 3){
if(($d = $this->getBoundedInt($sender, $args[2], 0, INT32_MAX)) === null){
if(($d = $this->getBoundedInt($sender, $args[2], 0, (int) (INT32_MAX / 20))) === null){
return false;
}
$duration = $d * 20; //ticks

View File

@ -77,7 +77,15 @@ class EnchantCommand extends VanillaCommand{
return true;
}
$item->addEnchantment(new EnchantmentInstance($enchantment, (int) ($args[2] ?? 1)));
$level = 1;
if(isset($args[2])){
$level = $this->getBoundedInt($sender, $args[2], 1, $enchantment->getMaxLevel());
if($level === null){
return false;
}
}
$item->addEnchantment(new EnchantmentInstance($enchantment, $level));
$player->getInventory()->setItemInHand($item);

View File

@ -55,7 +55,7 @@ class KillCommand extends VanillaCommand{
if(count($args) === 1){
if(!$sender->hasPermission("pocketmine.command.kill.other")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
@ -74,7 +74,7 @@ class KillCommand extends VanillaCommand{
if($sender instanceof Player){
if(!$sender->hasPermission("pocketmine.command.kill.self")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}

View File

@ -53,6 +53,9 @@ class OpCommand extends VanillaCommand{
}
$name = array_shift($args);
if(!Player::isValidUserName($name)){
throw new InvalidCommandSyntaxException();
}
$player = $sender->getServer()->getOfflinePlayer($name);
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.op.success", [$player->getName()]));

View File

@ -26,6 +26,8 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\lang\TranslationContainer;
use function microtime;
use function round;
class SaveCommand extends VanillaCommand{
@ -43,7 +45,8 @@ class SaveCommand extends VanillaCommand{
return true;
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.start"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.start"));
$start = microtime(true);
foreach($sender->getServer()->getOnlinePlayers() as $player){
$player->save();
@ -53,7 +56,7 @@ class SaveCommand extends VanillaCommand{
$level->save(true);
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.success"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.success", [round(microtime(true) - $start, 3)]));
return true;
}

View File

@ -108,13 +108,11 @@ class StatusCommand extends VanillaCommand{
foreach($server->getLevels() as $level){
$levelName = $level->getFolderName() !== $level->getName() ? " (" . $level->getName() . ")" : "";
$timeColor = ($level->getTickRate() > 1 or $level->getTickRateTime() > 40) ? TextFormat::RED : TextFormat::YELLOW;
$tickRate = $level->getTickRate() > 1 ? " (tick rate " . $level->getTickRate() . ")" : "";
$timeColor = $level->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
$sender->sendMessage(TextFormat::GOLD . "World \"{$level->getFolderName()}\"$levelName: " .
TextFormat::RED . number_format(count($level->getChunks())) . TextFormat::GREEN . " chunks, " .
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities, " .
TextFormat::RED . number_format(count($level->getTiles())) . TextFormat::GREEN . " tiles. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms" . $tickRate
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities. " .
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms"
);
}

View File

@ -50,33 +50,29 @@ class TimeCommand extends VanillaCommand{
if($args[0] === "start"){
if(!$sender->hasPermission("pocketmine.command.time.start")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->startTime();
$level->checkTime();
}
Command::broadcastCommandMessage($sender, "Restarted the time");
return true;
}elseif($args[0] === "stop"){
if(!$sender->hasPermission("pocketmine.command.time.stop")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->stopTime();
$level->checkTime();
}
Command::broadcastCommandMessage($sender, "Stopped the time");
return true;
}elseif($args[0] === "query"){
if(!$sender->hasPermission("pocketmine.command.time.query")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
@ -85,7 +81,7 @@ class TimeCommand extends VanillaCommand{
}else{
$level = $sender->getServer()->getDefaultLevel();
}
$sender->sendMessage(new TranslationContainer("commands.time.query", [$level->getTime()]));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString("commands.time.query", [$level->getTime()]));
return true;
}
@ -96,7 +92,7 @@ class TimeCommand extends VanillaCommand{
if($args[0] === "set"){
if(!$sender->hasPermission("pocketmine.command.time.set")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
@ -110,23 +106,19 @@ class TimeCommand extends VanillaCommand{
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->setTime($value);
$level->checkTime();
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.set", [$value]));
}elseif($args[0] === "add"){
if(!$sender->hasPermission("pocketmine.command.time.add")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}
$value = $this->getInteger($sender, $args[1], 0);
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->setTime($level->getTime() + $value);
$level->checkTime();
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.added", [$value]));
}else{

View File

@ -27,6 +27,7 @@ use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\TranslationContainer;
use pocketmine\Player;
use pocketmine\utils\TextFormat;
use function count;
use function implode;
@ -94,6 +95,9 @@ class WhitelistCommand extends VanillaCommand{
if($this->badPerm($sender, strtolower($args[0]))){
return false;
}
if(!Player::isValidUserName($args[1])){
throw new InvalidCommandSyntaxException();
}
switch(strtolower($args[0])){
case "add":
$sender->getServer()->getOfflinePlayer($args[1])->setWhitelisted(true);
@ -111,9 +115,13 @@ class WhitelistCommand extends VanillaCommand{
return true;
}
private function badPerm(CommandSender $sender, string $perm) : bool{
if(!$sender->hasPermission("pocketmine.command.whitelist.$perm")){
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
private function badPerm(CommandSender $sender, string $subcommand) : bool{
static $map = [
"on" => "enable",
"off" => "disable"
];
if(!$sender->hasPermission("pocketmine.command.whitelist." . ($map[$subcommand] ?? $subcommand))){
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
return true;
}

View File

@ -40,6 +40,11 @@ class Attribute{
public const ATTACK_DAMAGE = 8;
public const EXPERIENCE_LEVEL = 9;
public const EXPERIENCE = 10;
public const UNDERWATER_MOVEMENT = 11;
public const LUCK = 12;
public const FALL_DAMAGE = 13;
public const HORSE_JUMP_STRENGTH = 14;
public const ZOMBIE_SPAWN_REINFORCEMENTS = 15;
private $id;
protected $minValue;
@ -66,8 +71,11 @@ class Attribute{
self::addAttribute(self::ATTACK_DAMAGE, "minecraft:attack_damage", 0.00, 340282346638528859811704183484516925440.00, 1.00, false);
self::addAttribute(self::EXPERIENCE_LEVEL, "minecraft:player.level", 0.00, 24791.00, 0.00);
self::addAttribute(self::EXPERIENCE, "minecraft:player.experience", 0.00, 1.00, 0.00);
//TODO: minecraft:luck (for fishing?)
//TODO: minecraft:fall_damage
self::addAttribute(self::UNDERWATER_MOVEMENT, "minecraft:underwater_movement", 0.0, 340282346638528859811704183484516925440.0, 0.02);
self::addAttribute(self::LUCK, "minecraft:luck", -1024.0, 1024.0, 0.0);
self::addAttribute(self::FALL_DAMAGE, "minecraft:fall_damage", 0.0, 340282346638528859811704183484516925440.0, 1.0);
self::addAttribute(self::HORSE_JUMP_STRENGTH, "minecraft:horse.jump_strength", 0.0, 2.0, 0.7);
self::addAttribute(self::ZOMBIE_SPAWN_REINFORCEMENTS, "minecraft:zombie.spawn_reinforcements", 0.0, 1.0, 0.0);
}
/**

View File

@ -107,6 +107,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_TYPE_LONG = 7;
public const DATA_TYPE_VECTOR3F = 8;
/*
* Readers beware: this isn't a nice list. Some of the properties have different types for different entities, and
* are used for entirely different things.
*/
public const DATA_FLAGS = 0;
public const DATA_HEALTH = 1; //int (minecart/boat)
public const DATA_VARIANT = 2; //int
@ -124,17 +128,21 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_PADDLE_TIME_RIGHT = 14; //float
public const DATA_EXPERIENCE_VALUE = 15; //int (xp orb)
public const DATA_MINECART_DISPLAY_BLOCK = 16; //int (id | (data << 16))
public const DATA_HORSE_FLAGS = 16; //int
/* 16 (byte) used by wither skull */
public const DATA_MINECART_DISPLAY_OFFSET = 17; //int
public const DATA_SHOOTER_ID = 17; //long (used by arrows)
public const DATA_MINECART_HAS_DISPLAY = 18; //byte (must be 1 for minecart to show block inside)
//TODO: add more properties
public const DATA_HORSE_TYPE = 19; //byte
/* 20 (unknown)
* 21 (unknown) */
public const DATA_CHARGE_AMOUNT = 22; //int8, used for ghasts and also crossbow charging
public const DATA_ENDERMAN_HELD_ITEM_ID = 23; //short
public const DATA_ENTITY_AGE = 24; //short
/* 26 (byte) player-specific flags
* 27 (int) player "index"?
* 28 (block coords) bed position */
/* 25 (int) used by horse, (byte) used by witch */
public const DATA_PLAYER_FLAGS = 26; //byte
public const DATA_PLAYER_INDEX = 27; //int, used for marker colours and agent nametag colours
public const DATA_PLAYER_BED_POSITION = 28; //blockpos
public const DATA_FIREBALL_POWER_X = 29; //float
public const DATA_FIREBALL_POWER_Y = 30;
public const DATA_FIREBALL_POWER_Z = 31;
@ -145,62 +153,71 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_POTION_AUX_VALUE = 36; //short
public const DATA_LEAD_HOLDER_EID = 37; //long
public const DATA_SCALE = 38; //float
public const DATA_INTERACTIVE_TAG = 39; //string (button text)
public const DATA_NPC_SKIN_ID = 40; //string
public const DATA_URL_TAG = 41; //string
public const DATA_MAX_AIR = 42; //short
public const DATA_MARK_VARIANT = 43; //int
public const DATA_CONTAINER_TYPE = 44; //byte (ContainerComponent)
public const DATA_CONTAINER_BASE_SIZE = 45; //int (ContainerComponent)
public const DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH = 46; //int (used for llamas, inventory size is baseSize + thisProp * strength)
public const DATA_BLOCK_TARGET = 47; //block coords (ender crystal)
public const DATA_WITHER_INVULNERABLE_TICKS = 48; //int
public const DATA_WITHER_TARGET_1 = 49; //long
public const DATA_WITHER_TARGET_2 = 50; //long
public const DATA_WITHER_TARGET_3 = 51; //long
/* 52 (short) */
public const DATA_BOUNDING_BOX_WIDTH = 53; //float
public const DATA_BOUNDING_BOX_HEIGHT = 54; //float
public const DATA_FUSE_LENGTH = 55; //int
public const DATA_RIDER_SEAT_POSITION = 56; //vector3f
public const DATA_RIDER_ROTATION_LOCKED = 57; //byte
public const DATA_RIDER_MAX_ROTATION = 58; //float
public const DATA_RIDER_MIN_ROTATION = 59; //float
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 60; //float
public const DATA_AREA_EFFECT_CLOUD_WAITING = 61; //int
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 62; //int
/* 63 (int) shulker-related */
public const DATA_SHULKER_ATTACH_FACE = 64; //byte
/* 65 (short) shulker-related */
public const DATA_SHULKER_ATTACH_POS = 66; //block coords
public const DATA_TRADING_PLAYER_EID = 67; //long
/* 69 (byte) command-block */
public const DATA_COMMAND_BLOCK_COMMAND = 70; //string
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 71; //string
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 72; //byte
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 73; //byte
public const DATA_STRENGTH = 74; //int
public const DATA_MAX_STRENGTH = 75; //int
/* 76 (int) */
public const DATA_LIMITED_LIFE = 77;
public const DATA_ARMOR_STAND_POSE_INDEX = 78; //int
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; //int
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
public const DATA_BALLOON_ATTACHED_ENTITY = 84; //int64, entity unique ID of owner
public const DATA_PUFFERFISH_SIZE = 85; //byte
public const DATA_BOAT_BUBBLE_TIME = 86; //int (time in bubble column)
public const DATA_PLAYER_AGENT_EID = 87; //long
/* 88 (float) related to panda sitting
* 89 (float) related to panda sitting
* 90 (unknown) */
public const DATA_FLAGS2 = 91; //long (extended data flags)
/* 92 (float) related to panda lying down
* 93 (float) related to panda lying down */
public const DATA_HAS_NPC_COMPONENT = 39; //byte (???)
public const DATA_SKIN_ID = 40; //string
public const DATA_NPC_SKIN_ID = 41; //string
public const DATA_URL_TAG = 42; //string
public const DATA_MAX_AIR = 43; //short
public const DATA_MARK_VARIANT = 44; //int
public const DATA_CONTAINER_TYPE = 45; //byte (ContainerComponent)
public const DATA_CONTAINER_BASE_SIZE = 46; //int (ContainerComponent)
public const DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH = 47; //int (used for llamas, inventory size is baseSize + thisProp * strength)
public const DATA_BLOCK_TARGET = 48; //block coords (ender crystal)
public const DATA_WITHER_INVULNERABLE_TICKS = 49; //int
public const DATA_WITHER_TARGET_1 = 50; //long
public const DATA_WITHER_TARGET_2 = 51; //long
public const DATA_WITHER_TARGET_3 = 52; //long
/* 53 (short) */
public const DATA_BOUNDING_BOX_WIDTH = 54; //float
public const DATA_BOUNDING_BOX_HEIGHT = 55; //float
public const DATA_FUSE_LENGTH = 56; //int
public const DATA_RIDER_SEAT_POSITION = 57; //vector3f
public const DATA_RIDER_ROTATION_LOCKED = 58; //byte
public const DATA_RIDER_MAX_ROTATION = 59; //float
public const DATA_RIDER_MIN_ROTATION = 60; //float
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float
public const DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int
/* 64 (int) shulker-related */
public const DATA_SHULKER_ATTACH_FACE = 65; //byte
/* 66 (short) shulker-related */
public const DATA_SHULKER_ATTACH_POS = 67; //block coords
public const DATA_TRADING_PLAYER_EID = 68; //long
/* 70 (byte) command-block */
public const DATA_COMMAND_BLOCK_COMMAND = 71; //string
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 72; //string
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 73; //byte
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 74; //byte
public const DATA_STRENGTH = 75; //int
public const DATA_MAX_STRENGTH = 76; //int
/* 77 (int) */
public const DATA_LIMITED_LIFE = 78;
public const DATA_ARMOR_STAND_POSE_INDEX = 79; //int
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 80; //int
public const DATA_ALWAYS_SHOW_NAMETAG = 81; //byte: -1 = default, 0 = only when looked at, 1 = always
public const DATA_COLOR_2 = 82; //byte
/* 83 (unknown) */
public const DATA_SCORE_TAG = 84; //string
public const DATA_BALLOON_ATTACHED_ENTITY = 85; //int64, entity unique ID of owner
public const DATA_PUFFERFISH_SIZE = 86; //byte
public const DATA_BOAT_BUBBLE_TIME = 87; //int (time in bubble column)
public const DATA_PLAYER_AGENT_EID = 88; //long
/* 89 (float) related to panda sitting
* 90 (float) related to panda sitting */
public const DATA_EAT_COUNTER = 91; //int (used by pandas)
public const DATA_FLAGS2 = 92; //long (extended data flags)
/* 93 (float) related to panda lying down
* 94 (float) related to panda lying down */
public const DATA_AREA_EFFECT_CLOUD_DURATION = 95; //int
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 96; //int
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 97; //float, usually negative
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 98; //float
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 99; //int
public const DATA_INTERACTIVE_TAG = 100; //string (button text)
public const DATA_TRADE_TIER = 101; //int
public const DATA_MAX_TRADE_TIER = 102; //int
public const DATA_TRADE_XP = 103; //int
public const DATA_FLAG_ONFIRE = 0;
public const DATA_FLAG_SNEAKING = 1;
@ -273,6 +290,26 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_IN_SCAFFOLDING = 68;
public const DATA_FLAG_OVER_SCAFFOLDING = 69;
public const DATA_FLAG_FALL_THROUGH_SCAFFOLDING = 70;
public const DATA_FLAG_BLOCKING = 71; //shield
public const DATA_FLAG_DISABLE_BLOCKING = 72;
//73 is set when a player is attacked while using shield, unclear on purpose
//74 related to shield usage, needs further investigation
public const DATA_FLAG_SLEEPING = 75;
//76 related to sleeping, unclear usage
public const DATA_FLAG_TRADE_INTEREST = 77;
public const DATA_FLAG_DOOR_BREAKER = 78; //...
public const DATA_FLAG_BREAKING_OBSTRUCTION = 79;
public const DATA_FLAG_DOOR_OPENER = 80; //...
public const DATA_FLAG_ILLAGER_CAPTAIN = 81;
public const DATA_FLAG_STUNNED = 82;
public const DATA_FLAG_ROARING = 83;
public const DATA_FLAG_DELAYED_ATTACKING = 84;
public const DATA_FLAG_AVOIDING_MOBS = 85;
//86 used by RangedAttackGoal
//87 used by NearestAttackableTargetGoal
public const DATA_PLAYER_FLAG_SLEEP = 1;
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK
public static $entityCount = 1;
/** @var Entity[] */
@ -477,7 +514,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var int */
public $lastUpdate;
/** @var int */
public $fireTicks = 0;
protected $fireTicks = 0;
/** @var CompoundTag */
public $namedtag;
/** @var bool */
@ -525,6 +562,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var bool */
protected $constructed = false;
/** @var bool */
private $closeInFlight = false;
public function __construct(Level $level, CompoundTag $nbt){
$this->constructed = true;
@ -556,6 +595,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
throw new \InvalidStateException("Cannot create entities in unloaded chunks");
}
$this->motion = new Vector3(0, 0, 0);
if($this->namedtag->hasTag("Motion", ListTag::class)){
/** @var float[] $motion */
$motion = $this->namedtag->getListTag("Motion")->getAllValues();
@ -1093,8 +1133,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function setOnFire(int $seconds) : void{
$ticks = $seconds * 20;
if($ticks > $this->fireTicks){
$this->fireTicks = $ticks;
if($ticks > $this->getFireTicks()){
$this->setFireTicks($ticks);
}
$this->setGenericFlag(self::DATA_FLAG_ONFIRE, true);
@ -1109,8 +1149,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/**
* @param int $fireTicks
* @throws \InvalidArgumentException
*/
public function setFireTicks(int $fireTicks) : void{
if($fireTicks < 0 or $fireTicks > 0x7fff){
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
}
$this->fireTicks = $fireTicks;
}
@ -1160,6 +1204,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
protected function updateMovement(bool $teleport = false) : void{
//TODO: hack for client-side AI interference: prevent client sided movement when motion is 0
$this->setImmobile($this->motion->x == 0 and $this->motion->y == 0 and $this->motion->z == 0);
$diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2;
$diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2;
@ -1628,7 +1675,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
assert(abs($dx) <= 20 and abs($dy) <= 20 and abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
$list = $this->level->getCollisionCubes($this, $this->level->getTickRate() > 1 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
//TODO: bad hack here will cause unexpected behaviour under heavy lag
$list = $this->level->getCollisionCubes($this, $this->level->getTickRateTime() > 50 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
foreach($list as $bb){
$dy = $bb->calculateYOffset($this->boundingBox, $dy);
@ -2063,7 +2111,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* WARNING: Entities are unusable after this has been executed!
*/
public function close() : void{
if($this->closeInFlight){
return;
}
if(!$this->closed){
$this->closeInFlight = true;
(new EntityDespawnEvent($this))->call();
$this->closed = true;
@ -2082,6 +2135,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->namedtag = null;
$this->lastDamageCause = null;
$this->closeInFlight = false;
}
}
@ -2187,4 +2241,47 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function __toString(){
return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")";
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
*
* @return mixed
* @throws \ErrorException
*/
public function __get($name){
if($name === "fireTicks"){
return $this->fireTicks;
}
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
* @param mixed $value
*
* @throws \ErrorException
* @throws \InvalidArgumentException
*/
public function __set($name, $value){
if($name === "fireTicks"){
$this->setFireTicks($value);
}else{
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
}
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
*
* @return bool
*/
public function __isset($name){
return $name === "fireTicks";
}
}

View File

@ -60,6 +60,7 @@ use function array_merge;
use function array_rand;
use function array_values;
use function ceil;
use function in_array;
use function max;
use function min;
use function mt_rand;
@ -70,13 +71,6 @@ use const INT32_MIN;
class Human extends Creature implements ProjectileSource, InventoryHolder{
public const DATA_PLAYER_FLAG_SLEEP = 1;
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK
public const DATA_PLAYER_FLAGS = 26;
public const DATA_PLAYER_BED_POSITION = 28;
/** @var PlayerInventory */
protected $inventory;
@ -105,18 +99,36 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
public function __construct(Level $level, CompoundTag $nbt){
if($this->skin === null){
$skinTag = $nbt->getCompoundTag("Skin");
if($skinTag === null or !self::isValidSkin($skinTag->hasTag("Data", ByteArrayTag::class) ?
$skinTag->getByteArray("Data") :
$skinTag->getString("Data", "")
)){
if($skinTag === null){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
$this->skin = self::deserializeSkinNBT($skinTag); //this throws if the skin is invalid
}
parent::__construct($level, $nbt);
}
/**
* @param CompoundTag $skinTag
*
* @return Skin
* @throws \InvalidArgumentException
*/
protected static function deserializeSkinNBT(CompoundTag $skinTag) : Skin{
$skin = new Skin(
$skinTag->getString("Name"),
$skinTag->hasTag("Data", StringTag::class) ? $skinTag->getString("Data") : $skinTag->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
$skinTag->getByteArray("CapeData", ""),
$skinTag->getString("GeometryName", ""),
$skinTag->getByteArray("GeometryData", "")
);
$skin->validate();
return $skin;
}
/**
* @deprecated
*
* Checks the length of a supplied skin bitmap and returns whether the length is valid.
*
* @param string $skin
@ -124,7 +136,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @return bool
*/
public static function isValidSkin(string $skin) : bool{
return strlen($skin) === 64 * 64 * 4 or strlen($skin) === 64 * 32 * 4 or strlen($skin) === 128 * 128 * 4;
return in_array(strlen($skin), Skin::ACCEPTED_SKIN_SIZES, true);
}
/**
@ -156,10 +168,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @param Skin $skin
*/
public function setSkin(Skin $skin) : void{
if(!$skin->isValid()){
throw new \InvalidStateException("Specified skin is not valid, must be 8KiB or 16KiB");
}
$skin->validate();
$this->skin = $skin;
$this->skin->debloatGeometryData();
}
@ -203,18 +212,13 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$old = $attr->getValue();
$attr->setValue($new);
$reset = false;
// ranges: 18-20 (regen), 7-17 (none), 1-6 (no sprint), 0 (health depletion)
foreach([17, 6, 0] as $bound){
if(($old > $bound) !== ($new > $bound)){
$reset = true;
$this->foodTickTimer = 0;
break;
}
}
if($reset){
$this->foodTickTimer = 0;
}
}
public function getMaxFood() : float{
@ -301,7 +305,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$food = $this->getFood();
if($food > 0){
$food--;
$this->setFood($food);
$this->setFood(max($food, 0));
}
}
}
@ -578,7 +582,9 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
public function getXpDropAmount() : int{
return (int) min(100, $this->getCurrentTotalXp());
//this causes some XP to be lost on death when above level 1 (by design), dropping at most enough points for
//about 7.5 levels of XP.
return (int) min(100, 7 * $this->getXpLevel());
}
public function getInventory(){
@ -597,17 +603,6 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$this->setNameTag($this->namedtag->getString("NameTag"));
}
$skin = $this->namedtag->getCompoundTag("Skin");
if($skin !== null){
$this->setSkin(new Skin(
$skin->getString("Name"),
$skin->hasTag("Data", StringTag::class) ? $skin->getString("Data") : $skin->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
$skin->getByteArray("CapeData", ""),
$skin->getString("GeometryName", ""),
$skin->getByteArray("GeometryData", "")
));
}
$this->uuid = UUID::fromData((string) $this->getId(), $this->skin->getSkinData(), $this->getNameTag());
}
@ -846,9 +841,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
}
protected function sendSpawnPacket(Player $player) : void{
if(!$this->skin->isValid()){
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
}
$this->skin->validate();
if(!($this instanceof Player)){
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */

View File

@ -152,7 +152,7 @@ abstract class Living extends Entity implements Damageable{
}
public function setMaxHealth(int $amount) : void{
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount)->setDefaultValue($amount);
}
public function getAbsorption() : float{
@ -644,6 +644,10 @@ abstract class Living extends Entity implements Damageable{
foreach($ev->getDrops() as $item){
$this->getLevel()->dropItem($this, $item);
}
//TODO: check death conditions (must have been damaged by player < 5 seconds from death)
//TODO: allow this number to be manipulated during EntityDeathEvent
$this->level->dropExperience($this, $this->getXpDropAmount());
}
protected function onDeathUpdate(int $tickDiff) : bool{
@ -651,9 +655,6 @@ abstract class Living extends Entity implements Damageable{
$this->deadTicks += $tickDiff;
if($this->deadTicks >= $this->maxDeadTicks){
$this->endDeathAnimation();
//TODO: check death conditions (must have been damaged by player < 5 seconds from death)
$this->level->dropExperience($this, $this->getXpDropAmount());
}
}

View File

@ -23,11 +23,18 @@ declare(strict_types=1);
namespace pocketmine\entity;
use function implode;
use function in_array;
use function json_decode;
use function json_encode;
use function strlen;
class Skin{
public const ACCEPTED_SKIN_SIZES = [
64 * 32 * 4,
64 * 64 * 4,
128 * 128 * 4
];
/** @var string */
private $skinId;
@ -48,12 +55,34 @@ class Skin{
$this->geometryData = $geometryData;
}
/**
* @deprecated
* @return bool
*/
public function isValid() : bool{
return (
$this->skinId !== "" and
(($s = strlen($this->skinData)) === 16384 or $s === 8192 or $s === 65536) and
($this->capeData === "" or strlen($this->capeData) === 8192)
);
try{
$this->validate();
return true;
}catch(\InvalidArgumentException $e){
return false;
}
}
/**
* @throws \InvalidArgumentException
*/
public function validate() : void{
if($this->skinId === ""){
throw new \InvalidArgumentException("Skin ID must not be empty");
}
$len = strlen($this->skinData);
if(!in_array($len, self::ACCEPTED_SKIN_SIZES, true)){
throw new \InvalidArgumentException("Invalid skin data size $len bytes (allowed sizes: " . implode(", ", self::ACCEPTED_SKIN_SIZES) . ")");
}
if($this->capeData !== "" and strlen($this->capeData) !== 8192){
throw new \InvalidArgumentException("Invalid cape data size " . strlen($this->capeData) . " bytes (must be exactly 8192 bytes)");
}
//TODO: validate geometry
}
/**

View File

@ -28,6 +28,7 @@ use pocketmine\entity\Human;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\Player;
use function sqrt;
class ExperienceOrb extends Entity{
public const NETWORK_ID = self::XP_ORB;
@ -147,7 +148,7 @@ class ExperienceOrb extends Entity{
return null;
}
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
$entity = $this->level->getEntity($this->targetPlayerRuntimeId);
if($entity instanceof Human){
return $entity;
}
@ -156,7 +157,7 @@ class ExperienceOrb extends Entity{
}
public function setTargetPlayer(?Human $player) : void{
$this->targetPlayerRuntimeId = $player ? $player->getId() : null;
$this->targetPlayerRuntimeId = $player !== null ? $player->getId() : null;
}
public function entityBaseTick(int $tickDiff = 1) : bool{
@ -190,15 +191,15 @@ class ExperienceOrb extends Entity{
$this->setTargetPlayer($currentTarget);
if($currentTarget !== null){
$vector = $currentTarget->subtract($this)->add(0, $currentTarget->getEyeHeight() / 2, 0)->divide(self::MAX_TARGET_DISTANCE);
$vector = $currentTarget->add(0, $currentTarget->getEyeHeight() / 2, 0)->subtract($this)->divide(self::MAX_TARGET_DISTANCE);
$distance = $vector->length();
$oneMinusDistance = (1 - $distance) ** 2;
$distance = $vector->lengthSquared();
if($distance < 1){
$diff = $vector->normalize()->multiply(0.2 * (1 - sqrt($distance)) ** 2);
if($oneMinusDistance > 0){
$this->motion->x += $vector->x / $distance * $oneMinusDistance * 0.2;
$this->motion->y += $vector->y / $distance * $oneMinusDistance * 0.2;
$this->motion->z += $vector->z / $distance * $oneMinusDistance * 0.2;
$this->motion->x += $diff->x;
$this->motion->y += $diff->y;
$this->motion->z += $diff->z;
}
if($currentTarget->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){

View File

@ -123,7 +123,7 @@ class Arrow extends Projectile{
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->isCollided){
if($this->blockHit !== null){
$this->collideTicks += $tickDiff;
if($this->collideTicks > 1200){
$this->flagForDespawn();

View File

@ -50,8 +50,6 @@ use const PHP_INT_MAX;
abstract class Projectile extends Entity{
public const DATA_SHOOTER_ID = 17;
/** @var float */
protected $damage = 0.0;
@ -319,7 +317,7 @@ abstract class Projectile extends Entity{
$entityHit->attack($ev);
if($this->fireTicks > 0){
if($this->isOnFire()){
$ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
$ev->call();
if(!$ev->isCancelled()){

View File

@ -85,7 +85,7 @@ class SplashPotion extends Throwable{
if(!$this->willLinger()){
foreach($this->level->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
if($entity instanceof Living and $entity->isAlive()){
$distanceSquared = $entity->distanceSquared($this);
$distanceSquared = $entity->add(0, $entity->getEyeHeight(), 0)->distanceSquared($this);
if($distanceSquared > 16){ //4 blocks
continue;
}

View File

@ -67,10 +67,7 @@ class PlayerChangeSkinEvent extends PlayerEvent implements Cancellable{
* @throws \InvalidArgumentException if the specified skin is not valid
*/
public function setNewSkin(Skin $skin) : void{
if(!$skin->isValid()){
throw new \InvalidArgumentException("Skin format is invalid");
}
$skin->validate();
$this->newSkin = $skin;
}
}

View File

@ -27,7 +27,7 @@ use pocketmine\level\Position;
use pocketmine\Player;
/**
* Called when a player is respawned (or first time spawned)
* Called when a player is respawned
*/
class PlayerRespawnEvent extends PlayerEvent{
/** @var Position */

View File

@ -59,6 +59,9 @@ class AnvilInventory extends ContainerInventory{
public function onClose(Player $who) : void{
parent::onClose($who);
$this->dropContents($this->holder->getLevel(), $this->holder->add(0.5, 0.5, 0.5));
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
}

View File

@ -94,11 +94,12 @@ class ArmorInventory extends BaseInventory{
$target = [$target];
}
$armor = $this->getContents(true);
$pk = new MobArmorEquipmentPacket();
$pk->entityRuntimeId = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->head = $this->getHelmet();
$pk->chest = $this->getChestplate();
$pk->legs = $this->getLeggings();
$pk->feet = $this->getBoots();
$pk->encode();
foreach($target as $player){
@ -121,18 +122,19 @@ class ArmorInventory extends BaseInventory{
$target = [$target];
}
$armor = $this->getContents(true);
$pk = new MobArmorEquipmentPacket();
$pk->entityRuntimeId = $this->getHolder()->getId();
$pk->slots = $armor;
$pk->head = $this->getHelmet();
$pk->chest = $this->getChestplate();
$pk->legs = $this->getLeggings();
$pk->feet = $this->getBoots();
$pk->encode();
foreach($target as $player){
if($player === $this->getHolder()){
$pk2 = new InventoryContentPacket();
$pk2->windowId = $player->getWindowId($this);
$pk2->items = $armor;
$pk2->items = $this->getContents(true);
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);

View File

@ -253,11 +253,9 @@ abstract class BaseInventory implements Inventory{
public function canAddItem(Item $item) : bool{
$item = clone $item;
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasCompoundTag();
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->equals($slot, $checkDamage, $checkTags)){
if($item->equals($slot)){
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
$item->setCount($item->getCount() - $diff);
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\Server;
@ -56,24 +55,33 @@ class CraftingManager{
foreach($recipes as $recipe){
switch($recipe["type"]){
case 0:
case "shapeless":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerRecipe(new ShapelessRecipe(
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
));
break;
case 1:
case "shaped":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerRecipe(new ShapedRecipe(
$recipe["shape"],
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
));
break;
case 2:
case 3:
$result = $recipe["output"];
$resultItem = Item::jsonDeserialize($result);
$this->registerRecipe(new FurnaceRecipe($resultItem, ItemFactory::get($recipe["inputId"], $recipe["inputDamage"] ?? -1, 1)));
case "smelting":
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerRecipe(new FurnaceRecipe(
Item::jsonDeserialize($recipe["output"]),
Item::jsonDeserialize($recipe["input"]))
);
break;
default:
break;

View File

@ -59,6 +59,9 @@ class EnchantInventory extends ContainerInventory{
public function onClose(Player $who) : void{
parent::onClose($who);
$this->dropContents($this->holder->getLevel(), $this->holder->add(0.5, 0.5, 0.5));
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
}

View File

@ -32,6 +32,21 @@ use function array_pop;
use function count;
use function intdiv;
/**
* This transaction type is specialized for crafting validation. It shares most of the same semantics of the base
* inventory transaction type, but the requirement for validity is slightly different.
*
* It is expected that the actions in this transaction type will produce an **unbalanced result**, i.e. some inputs won't
* have corresponding outputs, and vice versa. The reason why is because the unmatched inputs are recipe inputs, and
* the unmatched outputs are recipe results.
*
* Therefore, the validity requirement is that the imbalance of the transaction should match the expected inputs and
* outputs of a registered crafting recipe.
*
* This transaction allows multiple repetitions of the same recipe to be crafted in a single batch. In the case of batch
* crafting, the number of unmatched inputs and outputs must be exactly divisible by the expected recipe ingredients and
* results, with no remainder. Any leftovers are expected to be emitted back to the crafting grid.
*/
class CraftingTransaction extends InventoryTransaction{
/** @var CraftingRecipe|null */
protected $recipe;

View File

@ -29,14 +29,28 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\Player;
use function array_keys;
use function assert;
use function count;
use function get_class;
use function min;
use function shuffle;
use function spl_object_hash;
/**
* This InventoryTransaction only allows doing Transaction between one / two inventories
* This is the basic type for an inventory transaction. This is used for moving items between inventories, dropping
* items and more. It allows transactions with multiple inputs and outputs.
*
* Validation **does not** depend on ordering. This means that the actions can appear in any order and still be valid.
* The only validity requirement for this transaction type is that the balance of items must add up to zero. This means:
* - No new outputs without matching input amounts
* - No inputs without matching output amounts
* - No userdata changes (item state, NBT, etc)
*
* A transaction is composed of "actions", which are pairs of inputs and outputs which target a specific itemstack in
* a specific location. There are multiple types of inventory actions which might be involved in a transaction.
*
* @see InventoryAction
*/
class InventoryTransaction{
protected $hasExecuted = false;
@ -75,6 +89,11 @@ class InventoryTransaction{
}
/**
* Returns an **unordered** set of actions involved in this transaction.
*
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
* significance and should not be relied on.
*
* @return InventoryAction[]
*/
public function getActions() : array{
@ -93,6 +112,19 @@ class InventoryTransaction{
}
}
/**
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
*/
private function shuffleActions() : void{
$keys = array_keys($this->actions);
shuffle($keys);
$actions = [];
foreach($keys as $key){
$actions[$key] = $this->actions[$key];
}
$this->actions = $actions;
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
@ -271,6 +303,8 @@ class InventoryTransaction{
return false;
}
$this->shuffleActions();
$this->validate();
if(!$this->callExecuteEvent()){

View File

@ -63,10 +63,10 @@ class Bow extends Tool{
$diff = $player->getItemUseDuration();
$p = $diff / 20;
$force = min((($p ** 2) + $p * 2) / 3, 1) * 2;
$baseForce = min((($p ** 2) + $p * 2) / 3, 1);
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $baseForce >= 1);
if($entity instanceof Projectile){
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
if($entity instanceof ArrowEntity){
@ -83,9 +83,9 @@ class Bow extends Tool{
if($this->hasEnchantment(Enchantment::FLAME)){
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
}
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
$ev = new EntityShootBowEvent($player, $this, $entity, $baseForce * 3);
if($force < 0.1 or $diff < 5){
if($baseForce < 0.1 or $diff < 5){
$ev->setCancelled();
}

View File

@ -46,7 +46,6 @@ use pocketmine\utils\Binary;
use function array_map;
use function base64_decode;
use function base64_encode;
use function bin2hex;
use function file_get_contents;
use function get_class;
use function hex2bin;
@ -216,7 +215,7 @@ class Item implements ItemIds, \JsonSerializable{
/**
* Sets the Item's NBT
*
* @param CompoundTag|string $tags
* @param CompoundTag|string|null $tags
*
* @return Item
*/
@ -224,7 +223,7 @@ class Item implements ItemIds, \JsonSerializable{
if($tags instanceof CompoundTag){
$this->setNamedTag($tags);
}else{
$this->tags = (string) $tags;
$this->tags = $tags === null ? "" : (string) $tags;
$this->cachedNBT = null;
}
@ -232,6 +231,9 @@ class Item implements ItemIds, \JsonSerializable{
}
/**
* @deprecated This method returns NBT serialized in a network-dependent format. Prefer use of getNamedTag() instead.
* @see Item::getNamedTag()
*
* Returns the serialized NBT of the Item
* @return string
*/
@ -871,7 +873,7 @@ class Item implements ItemIds, \JsonSerializable{
* @return string
*/
final public function __toString() : string{
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:" . base64_encode($this->getCompoundTag()) : "");
}
/**

View File

@ -300,16 +300,16 @@ class ItemFactory{
/**
* Returns an instance of the Item with the specified id, meta, count and NBT.
*
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string $tags
* @param int $id
* @param int $meta
* @param int $count
* @param CompoundTag|string|null $tags
*
* @return Item
* @throws \TypeError
*/
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag)){
public static function get(int $id, int $meta = 0, int $count = 1, $tags = null) : Item{
if(!is_string($tags) and !($tags instanceof CompoundTag) and $tags !== null){
throw new \TypeError("`tags` argument must be a string or CompoundTag instance, " . (is_object($tags) ? "instance of " . get_class($tags) : gettype($tags)) . " given");
}
@ -318,7 +318,7 @@ class ItemFactory{
$listed = self::$list[self::getListOffset($id)];
if($listed !== null){
$item = clone $listed;
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
}elseif($id >= 0 and $id < 256){ //intentionally excludes negatives because extended blocks aren't supported yet
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
* crafting ingredients with any-damage. */
$item = new ItemBlock($id, $meta);

View File

@ -204,6 +204,7 @@ interface ItemIds extends BlockIds{
public const DARK_OAK_DOOR = 431;
public const CHORUS_FRUIT = 432;
public const CHORUS_FRUIT_POPPED = 433;
public const BANNER_PATTERN = 434;
public const DRAGON_BREATH = 437;
public const SPLASH_POTION = 438;
@ -237,6 +238,14 @@ interface ItemIds extends BlockIds{
public const HEART_OF_THE_SEA = 467;
public const TURTLE_SHELL_PIECE = 468;
public const TURTLE_HELMET = 469;
public const PHANTOM_MEMBRANE = 470;
public const CROSSBOW = 471;
public const SPRUCE_SIGN = 472;
public const BIRCH_SIGN = 473;
public const JUNGLE_SIGN = 474;
public const ACACIA_SIGN = 475;
public const DARKOAK_SIGN = 476;
public const SWEET_BERRIES = 477;
public const COMPOUND = 499;
public const RECORD_13 = 500;
@ -252,4 +261,6 @@ interface ItemIds extends BlockIds{
public const RECORD_11 = 510;
public const RECORD_WAIT = 511;
public const SHIELD = 513;
}

View File

@ -74,7 +74,7 @@ class Explosion{
*/
public function __construct(Position $center, float $size, $what = null){
if(!$center->isValid()){
throw new \InvalidArgumentException("Position does not have a valid level");
throw new \InvalidArgumentException("Position does not have a valid world");
}
$this->source = $center;
$this->level = $center->getLevel();
@ -219,11 +219,10 @@ class Explosion{
$t = $this->level->getTileAt($block->x, $block->y, $block->z);
if($t instanceof Tile){
if($t instanceof Chest){
$t->unpair();
}
if($yieldDrops and $t instanceof Container){
if($t instanceof Chest){
$t->unpair();
}
$t->getInventory()->dropContents($this->level, $t->add(0.5, 0.5, 0.5));
}

View File

@ -26,6 +26,7 @@ declare(strict_types=1);
*/
namespace pocketmine\level;
use pocketmine\block\Air;
use pocketmine\block\Block;
use pocketmine\block\BlockFactory;
use pocketmine\entity\Entity;
@ -78,6 +79,7 @@ use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
@ -111,6 +113,8 @@ use function trim;
use const INT32_MAX;
use const INT32_MIN;
use const M_PI;
use const PHP_INT_MAX;
use const PHP_INT_MIN;
#include <rules/Level.h>
@ -261,11 +265,12 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelTimings */
public $timings;
/** @var int */
private $tickRate;
/** @var int */
public $tickRateTime = 0;
/** @var int */
/**
* @deprecated
* @var int
*/
public $tickRateCounter = 0;
/** @var bool */
@ -293,6 +298,19 @@ class Level implements ChunkManager, Metadatable{
return (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF);
}
/**
* Computes a small index relative to chunk base from the given coordinates.
*
* @param int $x
* @param int $y
* @param int $z
*
* @return int
*/
public static function chunkBlockHash(int $x, int $y, int $z) : int{
return ($y << 8) | (($z & 0xf) << 4) | ($x & 0xf);
}
public static function getBlockXYZ(int $hash, ?int &$x, ?int &$y, ?int &$z) : void{
$x = $hash >> 36;
$y = ($hash >> 28) & Level::Y_MASK; //it's always positive
@ -368,7 +386,8 @@ class Level implements ChunkManager, Metadatable{
$this->worldHeight = $this->provider->getWorldHeight();
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.preparing", [$this->displayName]));
$this->generator = GeneratorManager::getGenerator($this->provider->getGenerator());
$this->generator = GeneratorManager::getGenerator($this->provider->getGenerator(), true);
//TODO: validate generator options
$this->folderName = $name;
@ -397,19 +416,26 @@ class Level implements ChunkManager, Metadatable{
$this->timings = new LevelTimings($this);
$this->temporalPosition = new Position(0, 0, 0, $this);
$this->temporalVector = new Vector3(0, 0, 0);
$this->tickRate = 1;
}
/**
* @deprecated
* @return int
*/
public function getTickRate() : int{
return $this->tickRate;
return 1;
}
public function getTickRateTime() : float{
return $this->tickRateTime;
}
/**
* @deprecated does nothing
* @param int $tickRate
*/
public function setTickRate(int $tickRate){
$this->tickRate = $tickRate;
}
public function registerGeneratorToWorker(int $worker) : void{
@ -452,7 +478,7 @@ class Level implements ChunkManager, Metadatable{
public function close(){
if($this->closed){
throw new \InvalidStateException("Tried to close a level which is already closed");
throw new \InvalidStateException("Tried to close a world which is already closed");
}
foreach($this->chunks as $chunk){
@ -554,7 +580,8 @@ class Level implements ChunkManager, Metadatable{
}
/**
* @internal DO NOT use this from plugins, it's for internal use only. Use Server->unloadLevel() instead.
* @internal
* @see Server::unloadLevel()
*
* Unloads the current level from memory safely
*
@ -565,7 +592,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function unload(bool $force = false) : bool{
if($this->doingTick and !$force){
throw new \InvalidStateException("Cannot unload a level during level tick");
throw new \InvalidStateException("Cannot unload a world during world tick");
}
$ev = new LevelUnloadEvent($this);
@ -584,7 +611,7 @@ class Level implements ChunkManager, Metadatable{
$defaultLevel = $this->server->getDefaultLevel();
foreach($this->getPlayers() as $player){
if($this === $defaultLevel or $defaultLevel === null){
$player->close($player->getLeaveMessage(), "Forced default level unload");
$player->close($player->getLeaveMessage(), "Forced default world unload");
}elseif($defaultLevel instanceof Level){
$player->teleport($this->server->getDefaultLevel()->getSafeSpawn());
}
@ -602,7 +629,10 @@ class Level implements ChunkManager, Metadatable{
}
/**
* Gets the players being used in a specific chunk
* @deprecated WARNING: This function has a misleading name. Contrary to what the name might imply, this function
* DOES NOT return players who are IN a chunk, rather, it returns players who can SEE the chunk.
*
* Returns a list of players who have the target chunk within their view distance.
*
* @param int $chunkX
* @param int $chunkZ
@ -681,25 +711,25 @@ class Level implements ChunkManager, Metadatable{
}
public function registerChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ, bool $autoLoad = true){
$hash = $loader->getLoaderId();
$loaderId = $loader->getLoaderId();
if(!isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)])){
$this->chunkLoaders[$index] = [];
$this->playerLoaders[$index] = [];
}elseif(isset($this->chunkLoaders[$index][$hash])){
if(!isset($this->chunkLoaders[$chunkHash = Level::chunkHash($chunkX, $chunkZ)])){
$this->chunkLoaders[$chunkHash] = [];
$this->playerLoaders[$chunkHash] = [];
}elseif(isset($this->chunkLoaders[$chunkHash][$loaderId])){
return;
}
$this->chunkLoaders[$index][$hash] = $loader;
$this->chunkLoaders[$chunkHash][$loaderId] = $loader;
if($loader instanceof Player){
$this->playerLoaders[$index][$hash] = $loader;
$this->playerLoaders[$chunkHash][$loaderId] = $loader;
}
if(!isset($this->loaders[$hash])){
$this->loaderCounter[$hash] = 1;
$this->loaders[$hash] = $loader;
if(!isset($this->loaders[$loaderId])){
$this->loaderCounter[$loaderId] = 1;
$this->loaders[$loaderId] = $loader;
}else{
++$this->loaderCounter[$hash];
++$this->loaderCounter[$loaderId];
}
$this->cancelUnloadChunkRequest($chunkX, $chunkZ);
@ -710,59 +740,45 @@ class Level implements ChunkManager, Metadatable{
}
public function unregisterChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ){
$index = Level::chunkHash($chunkX, $chunkZ);
$hash = $loader->getLoaderId();
if(isset($this->chunkLoaders[$index][$hash])){
unset($this->chunkLoaders[$index][$hash]);
unset($this->playerLoaders[$index][$hash]);
if(count($this->chunkLoaders[$index]) === 0){
unset($this->chunkLoaders[$index]);
unset($this->playerLoaders[$index]);
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
$loaderId = $loader->getLoaderId();
if(isset($this->chunkLoaders[$chunkHash][$loaderId])){
unset($this->chunkLoaders[$chunkHash][$loaderId]);
unset($this->playerLoaders[$chunkHash][$loaderId]);
if(count($this->chunkLoaders[$chunkHash]) === 0){
unset($this->chunkLoaders[$chunkHash]);
unset($this->playerLoaders[$chunkHash]);
$this->unloadChunkRequest($chunkX, $chunkZ, true);
}
if(--$this->loaderCounter[$hash] === 0){
unset($this->loaderCounter[$hash]);
unset($this->loaders[$hash]);
if(--$this->loaderCounter[$loaderId] === 0){
unset($this->loaderCounter[$loaderId]);
unset($this->loaders[$loaderId]);
}
}
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
*/
public function checkTime(){
if($this->stopTime){
return;
}else{
++$this->time;
}
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* @internal
*
* @param Player ...$targets If empty, will send to all players in the level.
*/
public function sendTime(Player ...$targets){
$pk = new SetTimePacket();
$pk->time = $this->time;
$pk->time = $this->time & 0xffffffff; //avoid overflowing the field, since the packet uses an int32
$this->server->broadcastPacket(count($targets) > 0 ? $targets : $this->players, $pk);
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* @internal
*
* @param int $currentTick
*
*/
public function doTick(int $currentTick){
if($this->closed){
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
throw new \InvalidStateException("Attempted to tick a world which has been closed");
}
$this->timings->doTick->startTiming();
@ -776,7 +792,14 @@ class Level implements ChunkManager, Metadatable{
}
protected function actuallyDoTick(int $currentTick) : void{
$this->checkTime();
if(!$this->stopTime){
//this simulates an overflow, as would happen in any language which doesn't do stupid things to var types
if($this->time === PHP_INT_MAX){
$this->time = PHP_INT_MIN;
}else{
$this->time++;
}
}
$this->sunAnglePercentage = $this->computeSunAnglePercentage(); //Sun angle depends on the current time
$this->skyLightReduction = $this->computeSkyLightReduction(); //Sky light reduction depends on the sun angle
@ -959,7 +982,7 @@ class Level implements ChunkManager, Metadatable{
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE;
@ -981,7 +1004,7 @@ class Level implements ChunkManager, Metadatable{
$pk->blockRuntimeId = $b->getRuntimeId();
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
}
$pk->flags = $flags;
@ -1076,11 +1099,12 @@ class Level implements ChunkManager, Metadatable{
foreach($chunk->getSubChunks() as $Y => $subChunk){
if(!($subChunk instanceof EmptySubChunk)){
$k = mt_rand(0, 0xfffffffff); //36 bits
for($i = 0; $i < 3; ++$i){
$k = mt_rand(0, 0xfff);
$x = $k & 0x0f;
$y = ($k >> 4) & 0x0f;
$z = ($k >> 8) & 0x0f;
$k >>= 12;
$blockId = $subChunk->getBlockId($x, $y, $z);
if($this->randomTickBlocks[$blockId] !== null){
@ -1133,11 +1157,16 @@ class Level implements ChunkManager, Metadatable{
}
public function saveChunks(){
foreach($this->chunks as $chunk){
if(($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0) and $chunk->isGenerated()){
$this->provider->saveChunk($chunk);
$chunk->setChanged(false);
$this->timings->syncChunkSaveTimer->startTiming();
try{
foreach($this->chunks as $chunk){
if(($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0) and $chunk->isGenerated()){
$this->provider->saveChunk($chunk);
$chunk->setChanged(false);
}
}
}finally{
$this->timings->syncChunkSaveTimer->stopTiming();
}
}
@ -1423,14 +1452,14 @@ class Level implements ChunkManager, Metadatable{
*/
public function getBlockAt(int $x, int $y, int $z, bool $cached = true, bool $addToCache = true) : Block{
$fullState = 0;
$blockHash = null;
$relativeBlockHash = null;
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
if($this->isInWorld($x, $y, $z)){
$blockHash = Level::blockHash($x, $y, $z);
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
if($cached and isset($this->blockCache[$chunkHash][$blockHash])){
return $this->blockCache[$chunkHash][$blockHash];
if($cached and isset($this->blockCache[$chunkHash][$relativeBlockHash])){
return $this->blockCache[$chunkHash][$relativeBlockHash];
}
$chunk = $this->chunks[$chunkHash] ?? null;
@ -1448,8 +1477,8 @@ class Level implements ChunkManager, Metadatable{
$block->z = $z;
$block->level = $this;
if($addToCache and $blockHash !== null){
$this->blockCache[$chunkHash][$blockHash] = $block;
if($addToCache and $relativeBlockHash !== null){
$this->blockCache[$chunkHash][$relativeBlockHash] = $block;
}
return $block;
@ -1607,19 +1636,19 @@ class Level implements ChunkManager, Metadatable{
$block->clearCaches();
$chunkHash = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
$blockHash = Level::blockHash($pos->x, $pos->y, $pos->z);
$relativeBlockHash = Level::chunkBlockHash($pos->x, $pos->y, $pos->z);
unset($this->blockCache[$chunkHash][$blockHash]);
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
if($direct){
$this->sendBlocks($this->getChunkPlayers($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY);
unset($this->chunkCache[$chunkHash], $this->changedBlocks[$chunkHash][$blockHash]);
unset($this->chunkCache[$chunkHash], $this->changedBlocks[$chunkHash][$relativeBlockHash]);
}else{
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$chunkHash][$blockHash] = $block;
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $block;
}
foreach($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader){
@ -1769,7 +1798,7 @@ class Level implements ChunkManager, Metadatable{
if($player !== null){
$ev = new BlockBreakEvent($player, $target, $item, $player->isCreative(), $drops, $xpDrop);
if(($player->isSurvival() and !$target->isBreakable($item)) or $player->isSpectator()){
if($target instanceof Air or ($player->isSurvival() and !$target->isBreakable($item)) or $player->isSpectator()){
$ev->setCancelled();
}elseif($this->checkSpawnProtection($player, $target)){
$ev->setCancelled(); //set it to cancelled so plugins can bypass this
@ -1868,7 +1897,7 @@ class Level implements ChunkManager, Metadatable{
$clickVector = new Vector3(0.0, 0.0, 0.0);
}
if($blockReplace->y >= $this->worldHeight or $blockReplace->y < 0){
if(!$this->isInWorld($blockReplace->x, $blockReplace->y, $blockReplace->z)){
//TODO: build height limit messages for custom world heights and mcregion cap
return false;
}
@ -2212,14 +2241,14 @@ class Level implements ChunkManager, Metadatable{
return;
}
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
$blockHash = Level::blockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$blockHash]);
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
@ -2251,15 +2280,15 @@ class Level implements ChunkManager, Metadatable{
return;
}
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
$blockHash = Level::blockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$blockHash]);
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
@ -2461,9 +2490,9 @@ class Level implements ChunkManager, Metadatable{
* @param int $chunkX
* @param int $chunkZ
* @param Chunk|null $chunk
* @param bool $unload
* @param bool $deleteEntitiesAndTiles Whether to delete entities and tiles on the old chunk, or transfer them to the new one
*/
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null, bool $unload = true){
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null, bool $deleteEntitiesAndTiles = true){
if($chunk === null){
return;
}
@ -2474,7 +2503,16 @@ class Level implements ChunkManager, Metadatable{
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
$oldChunk = $this->getChunk($chunkX, $chunkZ, false);
if($oldChunk !== null and $oldChunk !== $chunk){
if($unload){
if($deleteEntitiesAndTiles){
foreach($oldChunk->getEntities() as $player){
if(!($player instanceof Player)){
continue;
}
$chunk->addEntity($player);
$oldChunk->removeEntity($player);
$player->chunk = $chunk;
}
//TODO: this causes chunkloaders to receive false "unloaded" notifications
$this->unloadChunk($chunkX, $chunkZ, false, false);
}else{
foreach($oldChunk->getEntities() as $entity){
@ -2666,10 +2704,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addEntity(Entity $entity){
if($entity->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to world");
}
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2687,7 +2725,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeEntity(Entity $entity){
if($entity->getLevel() !== $this){
throw new LevelException("Invalid Entity level");
throw new LevelException("Invalid Entity world");
}
if($entity instanceof Player){
@ -2706,10 +2744,10 @@ class Level implements ChunkManager, Metadatable{
*/
public function addTile(Tile $tile){
if($tile->isClosed()){
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to Level");
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to world");
}
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
$chunkX = $tile->getFloorX() >> 4;
@ -2732,7 +2770,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function removeTile(Tile $tile){
if($tile->getLevel() !== $this){
throw new LevelException("Invalid Tile level");
throw new LevelException("Invalid Tile world");
}
unset($this->tiles[$tile->getId()], $this->updateTiles[$tile->getId()]);
@ -2814,6 +2852,7 @@ class Level implements ChunkManager, Metadatable{
$loader->onChunkLoaded($chunk);
}
}else{
$this->server->getLogger()->debug("Newly loaded chunk $x $z has no loaders registered, will be unloaded at next available opportunity");
$this->unloadChunkRequest($x, $z);
}
@ -2867,7 +2906,12 @@ class Level implements ChunkManager, Metadatable{
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){
$this->provider->saveChunk($chunk);
$this->timings->syncChunkSaveTimer->startTiming();
try{
$this->provider->saveChunk($chunk);
}finally{
$this->timings->syncChunkSaveTimer->stopTiming();
}
}
}
@ -3063,35 +3107,31 @@ class Level implements ChunkManager, Metadatable{
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){
return false;
}
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
if(isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])){
return false;
}
}
}
$chunk = $this->getChunk($x, $z, true);
if(!$chunk->isPopulated()){
Timings::$populationTimer->startTiming();
$populate = true;
$this->chunkPopulationQueue[$index] = true;
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
if(isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])){
$populate = false;
break;
}
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
}
if($populate){
$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);
$workerId = $this->server->getAsyncPool()->selectWorker();
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
$task = new PopulationTask($this, $chunk);
$workerId = $this->server->getAsyncPool()->selectWorker();
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
Timings::$populationTimer->stopTiming();
return false;

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\level;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
class LevelTimings{
@ -62,6 +63,8 @@ class LevelTimings{
public $syncChunkLoadEntitiesTimer;
/** @var TimingsHandler */
public $syncChunkLoadTileEntitiesTimer;
/** @var TimingsHandler */
public $syncChunkSaveTimer;
public function __construct(Level $level){
$name = $level->getFolderName() . " - ";
@ -85,6 +88,9 @@ class LevelTimings{
$this->syncChunkLoadEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Entities");
$this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileEntities");
Timings::init(); //make sure the timer we want is available
$this->syncChunkSaveTimer = new TimingsHandler("** " . $name . "syncChunkSave", Timings::$worldSaveTimer);
$this->doTick = new TimingsHandler($name . "doTick");
}
}

View File

@ -64,7 +64,7 @@ class Position extends Vector3{
*/
public function getLevel(){
if($this->level !== null and $this->level->isClosed()){
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded Level");
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded world");
$this->level = null;
}
@ -82,7 +82,7 @@ class Position extends Vector3{
*/
public function setLevel(Level $level = null){
if($level !== null and $level->isClosed()){
throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used");
throw new \InvalidArgumentException("Specified world has been unloaded and cannot be used");
}
$this->level = $level;

View File

@ -74,10 +74,10 @@ class ChunkRequestTask extends AsyncTask{
$batch->isEncoded = true;
$level->chunkRequestCallback($this->chunkX, $this->chunkZ, $batch);
}else{
$server->getLogger()->error("Chunk request for level #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
$server->getLogger()->error("Chunk request for world #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
}
}else{
$server->getLogger()->debug("Dropped chunk task due to level not loaded");
$server->getLogger()->debug("Dropped chunk task due to world not loaded");
}
}
}

View File

@ -311,6 +311,7 @@ class LevelDB extends BaseLevelProvider{
$lightPopulated = true;
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
$binaryStream = new BinaryStream();
@ -326,6 +327,9 @@ class LevelDB extends BaseLevelProvider{
$binaryStream->setBuffer($data, 0);
$subChunkVersion = $binaryStream->getByte();
if($subChunkVersion < self::CURRENT_LEVEL_SUBCHUNK_VERSION){
$hasBeenUpgraded = true;
}
switch($subChunkVersion){
case 0:
@ -334,6 +338,7 @@ class LevelDB extends BaseLevelProvider{
if($chunkVersion < 4){
$blockSkyLight = $binaryStream->get(2048);
$blockLight = $binaryStream->get(2048);
$hasBeenUpgraded = true; //drop saved light
}else{
//Mojang didn't bother changing the subchunk version when they stopped saving sky light -_-
$blockSkyLight = "";
@ -453,6 +458,7 @@ class LevelDB extends BaseLevelProvider{
$chunk->setGenerated(true);
$chunk->setPopulated(true);
$chunk->setLightPopulated($lightPopulated);
$chunk->setChanged($hasBeenUpgraded); //trigger rewriting chunk to disk if it was converted from an older format
return $chunk;
}

View File

@ -46,6 +46,7 @@ use function str_pad;
use function stream_set_read_buffer;
use function stream_set_write_buffer;
use function strlen;
use function substr;
use function time;
use function touch;
use function unpack;
@ -123,9 +124,6 @@ class RegionLoader{
*/
public function readChunk(int $x, int $z) : ?string{
$index = self::getChunkOffset($x, $z);
if($index < 0 or $index >= 4096){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
$this->lastUsed = time();
@ -134,39 +132,57 @@ class RegionLoader{
}
fseek($this->filePointer, $this->locationTable[$index][0] << 12);
$length = Binary::readInt(fread($this->filePointer, 4));
$compression = ord(fgetc($this->filePointer));
$prefix = fread($this->filePointer, 4);
if($prefix === false or strlen($prefix) !== 4){
throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)");
}
$length = Binary::readInt($prefix);
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
if($length >= self::MAX_SECTOR_LENGTH){
$this->locationTable[$index][0] = ++$this->lastSector;
$this->locationTable[$index][1] = 1;
throw new CorruptedChunkException("Corrupted chunk header detected (sector count larger than max)");
throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")");
}
return null;
}
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)");
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)");
$this->locationTable[$index][1] = $length >> 12;
$this->writeLocationIndex($index);
}elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
}
$chunkData = fread($this->filePointer, $length);
if($chunkData === false or strlen($chunkData) !== $length){
throw new CorruptedChunkException("Corrupted chunk detected (unexpected end of file reading chunk data)");
}
$compression = ord($chunkData[0]);
if($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
}
$chunkData = fread($this->filePointer, $length - 1);
if($chunkData === false){
throw new CorruptedChunkException("Corrupted chunk detected (failed to read chunk data from disk)");
}
return $chunkData;
return substr($chunkData, 1);
}
/**
* @param int $x
* @param int $z
*
* @return bool
* @throws \InvalidArgumentException
*/
public function chunkExists(int $x, int $z) : bool{
return $this->isChunkGenerated(self::getChunkOffset($x, $z));
}
/**
* @param int $x
* @param int $z
* @param string $chunkData
*
* @throws ChunkException
* @throws \InvalidArgumentException
*/
public function writeChunk(int $x, int $z, string $chunkData){
$this->lastUsed = time();
@ -196,14 +212,40 @@ class RegionLoader{
}
}
/**
* @param int $x
* @param int $z
*
* @throws \InvalidArgumentException
*/
public function removeChunk(int $x, int $z){
$index = self::getChunkOffset($x, $z);
$this->locationTable[$index][0] = 0;
$this->locationTable[$index][1] = 0;
}
/**
* @param int $x
* @param int $z
*
* @return int
* @throws \InvalidArgumentException
*/
protected static function getChunkOffset(int $x, int $z) : int{
return $x + ($z << 5);
if($x < 0 or $x > 31 or $z < 0 or $z > 31){
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
}
return $x | ($z << 5);
}
/**
* @param int $offset
* @param int &$x
* @param int &$z
*/
protected static function getChunkCoords(int $offset, ?int &$x, ?int &$z) : void{
$x = $offset & 0x1f;
$z = ($offset >> 5) & 0x1f;
}
/**
@ -231,18 +273,23 @@ class RegionLoader{
}
$data = unpack("N*", $headerRaw);
/** @var int[] $usedOffsets */
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
if($offset !== 0){
fseek($this->filePointer, $offset << 12);
self::getChunkCoords($i, $x, $z);
$fileOffset = $offset << 12;
fseek($this->filePointer, $fileOffset);
if(fgetc($this->filePointer) === false){ //Try and read from the location
throw new CorruptedRegionException("Region file location offset points to invalid location");
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
}elseif(isset($usedOffsets[$offset])){
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
}else{
$usedOffsets[$offset] = true;
$usedOffsets[$offset] = $i;
}
}

View File

@ -33,10 +33,11 @@ use pocketmine\level\generator\populator\Ore;
use pocketmine\level\generator\populator\Populator;
use pocketmine\math\Vector3;
use pocketmine\utils\Random;
use function array_map;
use function count;
use function explode;
use function preg_match;
use function preg_match_all;
use function str_replace;
class Flat extends Generator{
/** @var Chunk */
@ -62,6 +63,11 @@ class Flat extends Generator{
return "flat";
}
/**
* @param array $options
*
* @throws InvalidGeneratorOptionsException
*/
public function __construct(array $options = []){
$this->options = $options;
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
@ -89,13 +95,28 @@ class Flat extends Generator{
}
}
/**
* @param string $layers
*
* @return int[][]
* @throws InvalidGeneratorOptionsException
*/
public static function parseLayers(string $layers) : array{
$result = [];
preg_match_all('#^(([0-9]*x|)([0-9]{1,3})(|:[0-9]{0,2}))$#m', str_replace(",", "\n", $layers), $matches);
$split = array_map('\trim', explode(',', $layers));
$y = 0;
foreach($matches[3] as $i => $b){
$b = ItemFactory::fromString($b . $matches[4][$i]);
$cnt = $matches[2][$i] === "" ? 1 : (int) $matches[2][$i];
foreach($split as $line){
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
if(count($matches) !== 3){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
}
$cnt = $matches[1] !== "" ? (int) $matches[1] : 1;
try{
$b = ItemFactory::fromString($matches[2])->getBlock();
}catch(\InvalidArgumentException $e){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\": " . $e->getMessage(), 0, $e);
}
for($cY = $y, $y += $cnt; $cY < $y; ++$cY){
$result[$cY] = [$b->getId(), $b->getDamage()];
}
@ -113,6 +134,7 @@ class Flat extends Generator{
$this->floorLevel = count($this->structure);
//TODO: more error checking
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
foreach($matches[2] as $i => $option){
$params = true;

View File

@ -58,6 +58,11 @@ abstract class Generator{
/** @var Random */
protected $random;
/**
* @param array $settings
*
* @throws InvalidGeneratorOptionsException
*/
abstract public function __construct(array $settings = []);

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