Compare commits

..

105 Commits

Author SHA1 Message Date
4fc5b9772a Release 3.14.2 2020-07-13 10:46:57 +01:00
5d4880b0a7 SendUsageTask: fixed json_encode() choking on player list keys 2020-07-11 20:14:04 +01:00
2b1a0e1e72 PlayerRespawnEvent: harden setRespawnPosition()
apparently plugins like to pass around positions which have null worlds, which aside from being quite stupid, also breaks a lot of stuff and makes it look like PM is to blame when it's just trying to make everything work the way it's supposed to ...
2020-07-10 20:37:45 +01:00
cd022f1592 EmotePacket: make FLAG_SERVER constant public 2020-07-10 20:02:32 +01:00
4ae3fd7734 Player: Reset spawn chunk send count if teleporting pre-spawn 2020-07-09 12:17:19 +01:00
b2249f93c0 TaskHandler: bail if given a task that already has a handler
This fixes undefined behaviour when scheduling the same task twice. This is usually accidental and almost never desirable.
Note that this still allows a task to be scheduled again after it has
been cancelled; it only disallows scheduling a task multiple times
concurrently.

This commit will probably break MyPlot and other plugins that have
self-scheduling tasks, but as far as I can tell those use-cases should
be replaced with self-cancelling repeating tasks anyway.
2020-07-08 11:02:33 +01:00
303344783a CheckTestCompletionTask: use TaskHandler->cancel() 2020-07-08 10:57:20 +01:00
75e0844ff5 MainLogger: log stack traces with CRITICAL level
maybe this will get people to send the whole thing instead of just the error message? ...
2020-07-08 10:45:15 +01:00
18fabf5466 3.14.2 is next 2020-07-08 10:32:07 +01:00
2751c59979 Release 3.14.1 2020-07-08 10:32:07 +01:00
d99ffbd66c Attribute: register lava_movement attribute
this is purely to fix crashes when decoding net packets
2020-07-08 10:21:20 +01:00
a34f3261cb event: harden APIs that accept arrays
plugin devs can't be relied on to pass the proper types to these APIs, and when the wrong types get passed it makes type errors appear from inside the internals.
2020-07-04 21:55:23 +01:00
8ce0022de6 protocol: added UUInventorySlotOffset constants 2020-07-04 21:37:37 +01:00
fb6491ddeb BanListCommand: sort output into lexical order 2020-07-03 11:23:00 +01:00
3b961d0e5f WhitelistCommand: sort output of /whitelist list into lexical order 2020-07-03 11:19:23 +01:00
a60fc4cc28 ListCommand: sort output into lexical order 2020-07-03 11:15:31 +01:00
b747899fdd PluginsCommand: sort plugins list into lexical order 2020-07-03 11:13:32 +01:00
57b6451e16 Fix projectile motion being changed by the ladder, close #3602 (#3631) 2020-06-27 21:18:39 +01:00
8cf025a2df Default isVerified to true (#3644) 2020-06-27 21:17:34 +01:00
8480ee82ea Player: track hardcoded window state, fixes crashes opening inventory on high-latency connections 2020-06-27 18:34:39 +01:00
a6c1b7bf9c InventoryTransactionPacket: added missing field for encode 2020-06-26 20:57:48 +01:00
c267137fde 3.14.1 is next 2020-06-26 14:19:02 +01:00
461bc94236 Release 3.14.0 2020-06-26 14:19:02 +01:00
4fed08bcd4 ProtocolInfo: fixed version number 2020-06-26 14:15:58 +01:00
e990c5a0a5 Protocol changes for 1.16.0 2020-06-26 14:06:41 +01:00
c616d9bb7c 3.13.2 is next 2020-06-26 12:31:56 +01:00
81051441ba Release 3.13.1 2020-06-26 12:31:56 +01:00
3ecae0db19 WindowTypes: fill in a couple of blanks 2020-06-26 12:04:11 +01:00
c5bbb2bcbc Move crafting action detection from InventoryTransactionPacket to Player 2020-06-26 11:59:30 +01:00
24a2889758 NetworkInventoryAction does not require an InventoryTransactionPacket 2020-06-26 11:51:06 +01:00
60b26a7ea8 NetworkBinaryStream: unknown byte preceding NBT is a version, not a count 2020-06-26 11:47:08 +01:00
22b52f03d1 Player: fixed formatting error in InteractPacket debug message 2020-06-26 11:44:41 +01:00
df76c02e7a Explicitly release server.lock file when shutdown the server. (#3619)
Previously, this relied on PHP itself to release locks during the resource destructor phase during process exit, but sometimes it doesn't for god knows what reason. This change makes the lock file get explicitly released before the process dies.
2020-06-26 11:41:39 +01:00
d343187e58 phpstan: drop 2 obsolete level 8 error patterns fixed by 763c8ebfe3 2020-06-23 12:57:25 +01:00
c5ad127854 BaseInventory: mark eventProcessor as nullable, fixes a phpstan level 8 error 2020-06-23 12:55:06 +01:00
0f6dc9082a lock phpstan at 0.12.29
0.12.30 starts reporting non-ignorable errors for overriding Thread::start() due to outdated stubs for pthreads that I can't replace
2020-06-22 20:57:12 +01:00
2b6dcbc2e2 BaseLang: fixed passing onlyPrefix to str_replace()'s count reference parameter 2020-06-22 20:34:23 +01:00
763c8ebfe3 Thread/Worker: drop nullability flag from start() (fixed in pthreads 3.2.0, which we require as a minimum) 2020-06-21 19:07:01 +01:00
c572e9bb6a phpstan: regenerate l7 baseline 2020-06-21 18:58:41 +01:00
89521f166d Explosion: account for multi-block structures, fixes #2767 2020-06-21 17:28:38 +01:00
49d3a42120 phpstan: make EntityEvent generic, fix a bunch of 'actual-problems' ignored errors 2020-06-21 00:27:32 +01:00
c523595e85 Rewrite TeleportCommand (sadly I can't make this commit any smaller)
this pile of shit was overdue a rewrite. The new version is much easier to understand.
2020-06-21 00:04:18 +01:00
7c7e4f2093 WhitelistCommand: fixed silence on unknown subcommand 2020-06-20 23:05:39 +01:00
88c1014f03 TimingsCommand: fixed silence when using a nonexisting subcommand 2020-06-20 23:04:08 +01:00
e32180ce93 phpstan: drop an obsolete phpstan-bugs ignoreError 2020-06-19 10:41:41 +01:00
e105578be0 LegacySkinAdapter: an extra check for resourcePatch (fixes 3 phpstan explicitMixed errors) 2020-06-18 20:08:38 +01:00
a9d98bdf73 phpstan: baseline our way into checkExplicitMixed territory 2020-06-18 18:07:21 +01:00
c601352777 Fixed wrong meta value when pick cake block. (#3593) 2020-06-18 11:14:57 +01:00
77c71e22b2 SkinAdapter::fromSkinData() may now throw InvalidSkinException
fixes a rogue TODO in LegacySkinAdapter and invalid skins maybe showing up as Steve instead of getting kicked off the server
2020-06-17 21:44:22 +01:00
1c13ba5656 Avoid parameter ordering bugs during packet decoding
A PhpStorm refactor could have side effects on code that directly reads stuff from the packet input stream in the arguments block, because those calls will get moved into a different order if the constructor gets refactored. This would, obviously, break packet decoding, so that's something we should avoid and really should not encourage.
2020-06-17 21:01:01 +01:00
f970be0e4d SkinImage: 128x64 is not a valid classic skin size
MC itself doesn't accept classic skins of this size.
2020-06-17 20:31:28 +01:00
11a3f9f1b9 VerifyLoginTask: fast-fail by checking header x5u before verifying signature
this is less costly, although it doesn't make any difference except in invalid cases.
2020-06-17 17:52:19 +01:00
09771849ae VerifyLoginTask optimisation: do not copy the entire LoginPacket into the worker thread
this is especially bad considering the fact that the cached buffer is copied. That said, it's only a few kilobytes so it's not a huge problem, but nonetheless...
2020-06-17 17:46:22 +01:00
57a310230a ScoreboardIdentityPacketEntry: added missing field default 2020-06-15 23:59:53 +01:00
130c55d9f1 EntityLink: remove rogue default ctor parameters 2020-06-15 23:58:11 +01:00
2712befa82 SkinData: fixed capeImage type violation (doesn't accept null) 2020-06-15 23:51:48 +01:00
a4e250a3e6 TextFormat: improved exception messages for PCRE failures 2020-06-15 23:43:01 +01:00
23b97d8e2d TextFormat: wrap all preg_replace() usages in a type-safe exception-throwing version
fixes 3 phpstan level 8 errors
2020-06-15 23:31:46 +01:00
1fb5043eb1 build/server-phar: fixed a phpstan level 8 error 2020-06-15 23:16:40 +01:00
b0b1b29de4 Chunk: specify list<int> for heightMap 2020-06-15 23:07:35 +01:00
1c3b641e37 ChunkUtils: be more specific in extension stub too 2020-06-15 23:07:05 +01:00
f3063e797f ChunkUtils: provide a more explicit parameter type 2020-06-15 23:05:52 +01:00
8dcc88712c ChunkUtils: fixed phpstan level 7 type inference error 2020-06-15 23:04:12 +01:00
04191ec44a Rail: specify type for local static variable
fixes a phpstan level 7 error
phpstan doesn't make any assumptions about local static variable types because analysing them would require too much work, apparently.
2020-06-15 22:47:20 +01:00
62ea7c93a9 added a dedicated InvalidSkinException 2020-06-15 21:48:24 +01:00
cf06b5b8cf Player: explicitly check for false return of dataPacket()
fixes a phpstan error on l7
this won't ever actually be a problem, but this isn't obvious from the type system.
2020-06-15 21:24:19 +01:00
a8ec51daac Player: do not assign maybe-false result of json_encode() to ModalFormRequestPacket
fixes a phpstan error on l7
2020-06-15 21:22:33 +01:00
6a7b77fee2 first look at making region writes reuse old space 2020-06-15 18:36:54 +01:00
da42c8d020 Bump phpunit/phpunit from 9.2.2 to 9.2.3 (#3588) 2020-06-15 16:30:50 +00:00
b902f9ded0 RegionLoader: fixed 2 phpstan level 7 errors (eof reading region header) 2020-06-15 15:09:37 +01:00
9bb8a8f761 RegionLoader: added utility function getProportionUnusedSpace() 2020-06-15 13:49:03 +01:00
63b14a083c RegionLoader: added utility function generateSectorMap()
this proved very useful while debugging some internal issues.
2020-06-15 13:48:17 +01:00
627a7c951a RegionLoader: added missing const import 2020-06-15 13:46:03 +01:00
bb2685ca65 RegionLocationTableEntry: cap firstSector at 16777216
this is the biggest sector start that the location table can represent, due to the binary format. Larger values than this will overflow and cause corruption.
This provides an effective limit of 64 GB on region files.
2020-06-15 13:23:08 +01:00
d38709a7ae RegionLoader: remove unused variable 2020-06-15 12:26:20 +01:00
b559a65346 RegionLoader: account for possible corrupted header pointing to itself 2020-06-15 12:13:42 +01:00
b92a2ded8a RegionLoader: check for zero sector count when loading location table
implementations shouldn't be writing location entries that have an offset but zero sectors, but just in case they do, we need to be aware of it.
2020-06-15 12:08:55 +01:00
22f25dfbdb RegionLocationTableEntry: require sector count to be at least 1 2020-06-15 12:05:48 +01:00
6bf840c72e RegionLoader: use actual null instead of zeroed entry for non-allocated chunks
this forces the code to be properly aware of non-allocated chunks, because it'll crash with NPE if it isn't.
2020-06-15 12:02:03 +01:00
745be19a56 RegionLoader: fixed regions ballooning when writing chunks to the end of file
we already have a region growth problem due to the lack of garbage collection, but this bug was making it worse. If the region already contained 1024 allocated chunks, 4MB of file space would get wasted before the next chunk would be appended to the file.
2020-06-14 23:40:33 +01:00
e05bee5ffb RegionLoader: do a full check for chunk overlaps during initial load 2020-06-14 22:39:01 +01:00
087ba0cc1d phpstan: update config to use new 0.12.26+ options 2020-06-14 19:57:36 +01:00
d8d994351b phpstan 0.12.29 2020-06-14 16:25:55 +01:00
0029efa370 Server: add getPlayerDataPath(), reduce logic duplication 2020-06-14 12:40:24 +01:00
df13e967fd imports cleanup 2020-06-14 10:27:15 +01:00
097c260dbb Eradicate all usages of strtoupper()
strtoupper() is an evil function whose behaviour depends on the system locale. mb_strtoupper() has more consistent behaviour.
2020-06-13 19:47:00 +01:00
a219b727f2 updated build/php submodule to pmmp/php-build-scripts@cec63c3093 2020-06-13 11:14:21 +01:00
710c162604 QueryRegenerateEvent: fixed possible type violation on listPlugins 2020-06-10 12:11:39 +01:00
409c8c1703 TimingsCommand: workaround a PHPStan type specifying bug 2020-06-10 12:11:10 +01:00
376926c700 TimingsCommand: fix missing type information for async task local storage 2020-06-10 12:10:33 +01:00
c3fabe833e FileWriteTask: mark as deprecated 2020-06-10 11:22:18 +01:00
3e09ff5350 EnchantTable: fix formatting issue [ci skip] 2020-06-10 10:54:22 +01:00
7255065106 LevelDB: stop passing false to places where it's not expected 2020-06-10 10:45:54 +01:00
a7f10d8ccf phpstan: ignore a FP (fixed in 0.12.26, but we can't upgrade yet) 2020-06-10 10:45:28 +01:00
76f1add3b3 Timezone: return false if date_parse() fails
I hate this, but I don't want to change it to throw right now because it'll create a bunch of extra work.
2020-06-10 10:34:34 +01:00
fcc9e62c65 CommandEnum: specify enumValues type
phpstan 0.12.26 starts reporting errors about the result of array_search() being given to some constructor or another because of the lack of key type specification.
2020-06-10 10:33:35 +01:00
42613618a5 phpstan: add LevelDB::get() stub to fix return type 2020-06-10 10:31:54 +01:00
1bbeb62457 Bump phpunit/phpunit from 9.2.1 to 9.2.2 (#3566) 2020-06-08 17:32:39 +00:00
64893426fa Bump phpunit/phpunit from 8.5.5 to 9.2.1 (#3557) 2020-06-06 10:13:46 +00:00
3d50aafcc4 ShapedRecipe: remove a curly-brace array-access that nothing noticed 2020-06-06 11:12:45 +01:00
50fed41642 README: we're now on travis-ci.com 2020-06-05 13:02:31 +01:00
3f971a0c65 phpstan: fixed analyze failure caused by a27b29897c 2020-06-05 12:58:18 +01:00
a27b29897c TimingsCommand: tell operators what's going on for audit purposes
previously nobody except the person who was managing timings would know that timings was running, being pasted or whatever else. Since timings can impact performance (and, for example, block the main thread when writing timings to a file), access to it should be logged so that server owners know what's going on.
2020-06-05 11:12:36 +01:00
a90132a30e added missing 3.12.6 changelog 2020-06-04 20:38:06 +01:00
dfbd857771 3.13.1 is next 2020-06-04 20:01:28 +01:00
178 changed files with 5253 additions and 883 deletions

View File

@ -3,7 +3,7 @@
<b>A highly customisable, open source server software for Minecraft: Bedrock Edition written in PHP</b>
</p>
[![Build Status](https://travis-ci.org/pmmp/PocketMine-MP.svg?branch=master)](https://travis-ci.org/pmmp/PocketMine-MP)
[![Build Status](https://travis-ci.com/pmmp/PocketMine-MP.svg?branch=master)](https://travis-ci.com/pmmp/PocketMine-MP)
## Getting started
- [Documentation](http://pmmp.readthedocs.org/)

View File

@ -49,7 +49,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*
* @return string[]
*/
function preg_quote_array(array $strings, string $delim = null) : array{
function preg_quote_array(array $strings, string $delim) : array{
return array_map(function(string $str) use ($delim) : string{ return preg_quote($str, $delim); }, $strings);
}

View File

@ -50,4 +50,9 @@ Plugin developers should **only** update their required API to this version if y
- Fixed absorption hearts not being consumed.
# 3.12.5
- Fixed broken attack cooldowns.
- Fixed broken attack cooldowns.
# 3.12.6
- Fixed entities not getting movement updates after teleports.
- Fixed slow flight in spectator mode when starting from the ground and after teleportation.
- Errors communicating with the crash archive on automatic crash submission are now logged.

View File

@ -109,3 +109,23 @@ AsyncTask thread-local storage has been improved, making it simpler and easier t
- `Utils::recursiveUnlink()`
- `Terminal::write()`
- `Terminal::writeLine()`
# 3.13.1
- Fixed issues with `server.lock` not being unlocked on some platforms. Now, the server explicitly releases it before exiting.
- `/timings` now sends a usage message when using an unknown subcommand. Previously, it would just give no output.
- `/whitelist` now sends a usage message when using an unknown subcommand. Previously, it would just give no output.
- The output from `/timings` is now broadcasted on the `pocketmine.broadcast.admin` broadcast channel for auditability, similarly to other operator commands.
- Fixed `ShapedRecipe` deprecation warning on PHP 7.4.
- Fixed some potential crashes with Bedrock worlds when chunk data is corrupted or missing.
- Fixed a bug in region handling that caused region loaders to overestimate the amount of space used in the file. This resulted in an up to 4 MB growth of the file size every time the region was reloaded after writing a chunk.
- Region handlers now try to reuse free space in region files before putting the chunk at the end of the file. Previously, space was only reused if the new version of the chunk was <= the size of the old. This fixes endless growth of region files.
- Regions now never directly overwrite old copies of chunks when saving; instead they try to find an alternative location (preferring unused space within the file first). This avoids chunk corruption on power failure (the old copy of the chunk won't be damaged, so a rollback might occur instead), and as happy side effect, causes oversized regions to gradually shrink towards their most packed state over time, saving disk space.
- Regions now have a hard size cap at 64 GB. This is because the header pointers will overflow beyond 64 GB (besides, a normal region shouldn't be this big anyway).
- Fixed a crash that could occur when reading a too-short region header.
- `VerifyLoginTask` now only copies JWTs to verify instead of the entire login packet. This reduces the amount of data copied between threads, improving performance.
- Added a fast-fail check to `VerifyLoginTask` by checking the JWT header's `x5u` against the expected public key.
- `Skin->validate()` now throws `InvalidSkinException` instead of `\InvalidArgumentException`.
- A debug message is now logged when a player is kicked for having an invalid skin, giving a brief line of detail why.
- Fixed players not being kicked for having an invalid `resourcePatch`.
- Fixed block meta value of cake being preserved when using pick-block.
- Fixed explosions not fully destroying multi-block objects like beds and doors.

25
changelogs/3.14.md Normal file
View File

@ -0,0 +1,25 @@
**For Minecraft: Bedrock Edition 1.16.0**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.14.0
- Added support for Minecraft: Bedrock Edition 1.16.0.
- Removed compatibility with 1.14.60.
## Known issues (please don't open issues for these)
- Walls don't connect to each other
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
- New blocks, items & mobs aren't implemented
- Nether doesn't exist
# 3.14.1
- All skins are now trusted, bypassing the client-side trusted skin setting. Note that this means that NSFW skin filtering will **not** apply.
- Fixed projectile motion being altered by ladders.
- Fixed client-sided crashes when pressing E repeatedly very quickly on a high-latency connection.
- `/plugins`, `/whitelist list`, `/banlist` and `/list` now show output in alphabetical order.
- Some `pocketmine\event` APIs which accept arrays now have more robust type checking, fixing type errors caused by plugin input occurring in core code.
- `Attribute::getAttributeByName()` is now aware of the `minecraft:lava_movement` attribute.

View File

@ -38,10 +38,10 @@
"ocramius/package-versions": "^1.5"
},
"require-dev": {
"phpstan/phpstan": "^0.12.25",
"phpstan/phpstan": "0.12.29",
"phpstan/phpstan-phpunit": "^0.12.6",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^8.5"
"phpunit/phpunit": "^9.2"
},
"autoload": {
"psr-4": {

842
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
includes:
- tests/phpstan/configs/actual-problems.neon
- tests/phpstan/configs/check-explicit-mixed-baseline.neon
- tests/phpstan/configs/com-dotnet-magic.neon
- tests/phpstan/configs/custom-leveldb.neon
- tests/phpstan/configs/gc-hacks.neon
@ -16,8 +17,10 @@ includes:
parameters:
level: 8
autoload_files:
checkExplicitMixed: true
bootstrapFiles:
- tests/phpstan/bootstrap.php
scanFiles:
- src/pocketmine/PocketMine.php
- build/make-release.php
- build/server-phar.php
@ -32,6 +35,7 @@ parameters:
stubFiles:
- tests/phpstan/stubs/pthreads.stub
- tests/phpstan/stubs/chunkutils.stub
- tests/phpstan/stubs/leveldb.stub
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
staticReflectionClassNamePatterns:
- "#^COM$#"

View File

@ -50,6 +50,7 @@ use function is_object;
use function is_resource;
use function is_string;
use function json_encode;
use function mb_strtoupper;
use function min;
use function mkdir;
use function preg_match;
@ -58,7 +59,6 @@ use function round;
use function spl_object_hash;
use function sprintf;
use function strlen;
use function strtoupper;
use function substr;
use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_SLASHES;
@ -127,7 +127,7 @@ class MemoryManager{
if($m <= 0){
$defaultMemory = 0;
}else{
switch(strtoupper($matches[2])){
switch(mb_strtoupper($matches[2])){
case "K":
$defaultMemory = $m / 1024;
break;

View File

@ -33,6 +33,7 @@ use pocketmine\entity\Effect;
use pocketmine\entity\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\InvalidSkinException;
use pocketmine\entity\object\ItemEntity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\Skin;
@ -112,6 +113,7 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
@ -147,6 +149,7 @@ use pocketmine\network\mcpe\protocol\types\CommandParameter;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\GameMode;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor;
use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
@ -154,6 +157,8 @@ use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton;
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
use pocketmine\network\mcpe\protocol\types\SkinData;
use pocketmine\network\mcpe\protocol\types\SkinImage;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\VerifyLoginTask;
@ -171,7 +176,9 @@ use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\UUID;
use function abs;
use function array_key_exists;
use function array_merge;
use function array_values;
use function assert;
use function base64_decode;
use function ceil;
@ -222,6 +229,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
private const RESOURCE_PACK_CHUNK_SIZE = 128 * 1024; //128KB
//TODO: HACK!
//these IDs are used for 1.16 to restore 1.14ish crafting & inventory behaviour; since they don't seem to have any
//effect on the behaviour of inventory transactions I don't currently plan to integrate these into the main system.
private const RESERVED_WINDOW_ID_RANGE_START = ContainerIds::LAST - 10;
private const RESERVED_WINDOW_ID_RANGE_END = ContainerIds::LAST;
public const HARDCODED_CRAFTING_GRID_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 1;
public const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
/**
* Validates the given username.
*/
@ -301,6 +316,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var CraftingTransaction|null */
protected $craftingTransaction = null;
/**
* TODO: HACK! This tracks GUIs for inventories that the server considers "always open" so that the client can't
* open them twice. (1.16 hack)
* @var true[]
* @phpstan-var array<int, true>
* @internal
*/
public $openHardcodedWindows = [];
/** @var int */
protected $messageCounter = 2;
/** @var bool */
@ -742,7 +766,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$data->aliases = new CommandEnum();
$data->aliases->enumName = ucfirst($command->getName()) . "Aliases";
$data->aliases->enumValues = $aliases;
$data->aliases->enumValues = array_values($aliases);
}
$pk->commandData[$command->getName()] = $data;
@ -1227,11 +1251,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
$this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level);
$pk = new SetSpawnPositionPacket();
$pk->x = $this->spawnPosition->getFloorX();
$pk->y = $this->spawnPosition->getFloorY();
$pk->z = $this->spawnPosition->getFloorZ();
$pk->x = $pk->x2 = $this->spawnPosition->getFloorX();
$pk->y = $pk->y2 = $this->spawnPosition->getFloorY();
$pk->z = $pk->z2 = $this->spawnPosition->getFloorZ();
$pk->dimension = DimensionIds::OVERWORLD;
$pk->spawnType = SetSpawnPositionPacket::TYPE_PLAYER_SPAWN;
$pk->spawnForced = false;
$this->dataPacket($pk);
}
@ -1956,9 +1981,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
true
);
$skin = SkinAdapterSingleton::get()->fromSkinData($skinData);
if(!$skin->isValid()){
try{
$skin = SkinAdapterSingleton::get()->fromSkinData($skinData);
$skin->validate();
}catch(InvalidSkinException $e){
$this->server->getLogger()->debug("$this->username: Invalid skin: " . $e->getMessage());
$this->close("", "disconnectionScreen.invalidSkin");
return true;
@ -2197,7 +2224,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk->pitch = $this->pitch;
$pk->yaw = $this->yaw;
$pk->seed = -1;
$pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly
$pk->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
$pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode());
$pk->difficulty = $this->level->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX();
@ -2364,7 +2391,20 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var InventoryAction[] $actions */
$actions = [];
$isCraftingPart = false;
$isFinalCraftingPart = false;
foreach($packet->actions as $networkInventoryAction){
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and (
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
)
){
$isCraftingPart = true;
if($networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT){
$isFinalCraftingPart = true;
}
}
try{
$action = $networkInventoryAction->createInventoryAction($this);
if($action !== null){
@ -2377,7 +2417,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($packet->isCraftingPart){
if($isCraftingPart){
if($this->craftingTransaction === null){
$this->craftingTransaction = new CraftingTransaction($this, $actions);
}else{
@ -2386,7 +2426,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($packet->isFinalCraftingPart){
if($isFinalCraftingPart){
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
//trying to execute it
@ -2764,8 +2804,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case InteractPacket::ACTION_LEAVE_VEHICLE:
case InteractPacket::ACTION_MOUSEOVER:
break; //TODO: handle these
case InteractPacket::ACTION_OPEN_INVENTORY:
if($target === $this && !array_key_exists($windowId = self::HARDCODED_INVENTORY_WINDOW_ID, $this->openHardcodedWindows)){
//TODO: HACK! this restores 1.14ish behaviour, but this should be able to be listened to and
//controlled by plugins. However, the player is always a subscriber to their own inventory so it
//doesn't integrate well with the regular container system right now.
$this->openHardcodedWindows[$windowId] = true;
$pk = new ContainerOpenPacket();
$pk->windowId = $windowId;
$pk->type = WindowTypes::INVENTORY;
$pk->x = $pk->y = $pk->z = 0;
$pk->entityUniqueId = $this->getId();
$this->sendDataPacket($pk);
break;
}
return false;
default:
$this->server->getLogger()->debug("Unhandled/unknown interaction type " . $packet->action . "received from " . $this->getName());
$this->server->getLogger()->debug("Unhandled/unknown interaction type " . $packet->action . " received from " . $this->getName());
return false;
}
@ -2977,18 +3032,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleContainerClose(ContainerClosePacket $packet) : bool{
if(!$this->spawned or $packet->windowId === 0){
if(!$this->spawned){
return true;
}
$this->doCloseInventory();
if(array_key_exists($packet->windowId, $this->openHardcodedWindows)){
unset($this->openHardcodedWindows[$packet->windowId]);
$pk = new ContainerClosePacket();
$pk->windowId = $packet->windowId;
$this->sendDataPacket($pk);
return true;
}
if(isset($this->windowIndex[$packet->windowId])){
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
$this->removeWindow($this->windowIndex[$packet->windowId]);
return true;
}elseif($packet->windowId === 255){
//Closed a fake window
//removeWindow handles sending the appropriate
return true;
}
@ -3514,14 +3574,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* Sends a Form to the player, or queue to send it if a form is already open.
*/
public function sendForm(Form $form) : void{
$formData = json_encode($form);
if($formData === false){
throw new \InvalidArgumentException("Failed to encode form JSON: " . json_last_error_msg());
}
$id = $this->formIdCounter++;
$pk = new ModalFormRequestPacket();
$pk->formId = $id;
$pk->formData = json_encode($form);
if($pk->formData === false){
throw new \InvalidArgumentException("Failed to encode form JSON: " . json_last_error_msg());
}
if($this->dataPacket($pk)){
$pk->formData = $formData;
if($this->dataPacket($pk) !== false){
$this->forms[$id] = $form;
}
}
@ -3862,6 +3923,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->resetFallDistance();
$this->nextChunkOrderRun = 0;
if($this->spawnChunkLoadCount !== -1){
$this->spawnChunkLoadCount = 0;
}
$this->stopSleep();
//TODO: workaround for player last pos not getting updated
@ -3957,7 +4021,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if($forceId === null){
$cnt = $this->windowCnt;
do{
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % ContainerIds::LAST);
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % self::RESERVED_WINDOW_ID_RANGE_START);
if($cnt === $this->windowCnt){ //wraparound, no free slots
throw new \InvalidStateException("No free window IDs found");
}
@ -3965,7 +4029,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->windowCnt = $cnt;
}else{
$cnt = $forceId;
if(isset($this->windowIndex[$cnt])){
if(isset($this->windowIndex[$cnt]) or ($cnt >= self::RESERVED_WINDOW_ID_RANGE_START && $cnt <= self::RESERVED_WINDOW_ID_RANGE_END)){
throw new \InvalidArgumentException("Requested force ID $forceId already in use");
}
}

View File

@ -289,6 +289,14 @@ namespace pocketmine {
echo Terminal::$FORMAT_RESET . PHP_EOL;
if(!flock(\pocketmine\LOCK_FILE, LOCK_UN)){
critical_error("Failed to release the server.lock file.");
}
if(!fclose(\pocketmine\LOCK_FILE)){
critical_error("Could not close server.lock resource.");
}
exit($exitCode);
}

View File

@ -693,29 +693,32 @@ class Server{
return $result;
}
private function getPlayerDataPath(string $username) : string{
return $this->getDataPath() . '/players/' . strtolower($username) . '.dat';
}
/**
* Returns whether the server has stored any saved data for this player.
*/
public function hasOfflinePlayerData(string $name) : bool{
$name = strtolower($name);
return file_exists($this->getDataPath() . "players/$name.dat");
return file_exists($this->getPlayerDataPath($name));
}
public function getOfflinePlayerData(string $name) : CompoundTag{
$name = strtolower($name);
$path = $this->getDataPath() . "players/";
$path = $this->getPlayerDataPath($name);
if($this->shouldSavePlayerData()){
if(file_exists($path . "$name.dat")){
if(file_exists($path)){
try{
$nbt = new BigEndianNBTStream();
$compound = $nbt->readCompressed(file_get_contents($path . "$name.dat"));
$compound = $nbt->readCompressed(file_get_contents($path));
if(!($compound instanceof CompoundTag)){
throw new \RuntimeException("Invalid data found in \"$name.dat\", expected " . CompoundTag::class . ", got " . (is_object($compound) ? get_class($compound) : gettype($compound)));
}
return $compound;
}catch(\Throwable $e){ //zlib decode error / corrupt data
rename($path . "$name.dat", $path . "$name.dat.bak");
rename($path, $path . '.bak');
$this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerCorrupted", [$name]));
}
}else{
@ -776,7 +779,7 @@ class Server{
if(!$ev->isCancelled()){
$nbt = new BigEndianNBTStream();
try{
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
file_put_contents($this->getPlayerDataPath($name), $nbt->writeCompressed($ev->getSaveData()));
}catch(\Throwable $e){
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
$this->logger->logException($e);

View File

@ -76,11 +76,9 @@ abstract class Thread extends \Thread{
}
/**
* @param int|null $options TODO: pthreads bug
*
* @return bool
*/
public function start(?int $options = PTHREADS_INHERIT_ALL){
public function start(int $options = PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if($this->getClassLoader() === null){

View File

@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
const _VERSION_INFO_INCLUDED = true;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.13.0";
const BASE_VERSION = "3.14.2";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -76,11 +76,9 @@ abstract class Worker extends \Worker{
}
/**
* @param int|null $options TODO: pthreads bug
*
* @return bool
*/
public function start(?int $options = PTHREADS_INHERIT_ALL){
public function start(int $options = PTHREADS_INHERIT_ALL){
ThreadManager::getInstance()->add($this);
if($this->getClassLoader() === null){

View File

@ -36,7 +36,7 @@ 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\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\Player;
use pocketmine\plugin\Plugin;
use function array_merge;

View File

@ -25,7 +25,7 @@ namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\level\Position;
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use function min;
/**

View File

@ -109,6 +109,10 @@ class Cake extends Transparent implements FoodSource{
return true;
}
public function getVariantBitmask() : int{
return 0;
}
/**
* @return Block
*/

View File

@ -25,7 +25,10 @@ namespace pocketmine\block;
use pocketmine\inventory\CraftingGrid;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\types\WindowTypes;
use pocketmine\Player;
use function array_key_exists;
class CraftingTable extends Solid{
@ -50,6 +53,19 @@ class CraftingTable extends Solid{
public function onActivate(Item $item, Player $player = null) : bool{
if($player instanceof Player){
$player->setCraftingGrid(new CraftingGrid($player, CraftingGrid::SIZE_BIG));
if(!array_key_exists($windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID, $player->openHardcodedWindows)){
//TODO: HACK! crafting grid doesn't fit very well into the current PM container system, so this hack allows
//it to carry on working approximately the same way as it did in 1.14
$pk = new ContainerOpenPacket();
$pk->windowId = $windowId;
$pk->type = WindowTypes::WORKBENCH;
$pk->x = $this->getFloorX();
$pk->y = $this->getFloorY();
$pk->z = $this->getFloorZ();
$player->sendDataPacket($pk);
$player->openHardcodedWindows[$windowId] = true;
}
}
return true;

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
@ -58,7 +59,7 @@ class Ladder extends Transparent{
}
public function onEntityCollide(Entity $entity) : void{
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
if($entity instanceof Living and $entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}

View File

@ -71,6 +71,7 @@ class Rail extends BaseRail{
}
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
/** @var int[] $horizontal */
static $horizontal = [
Vector3::SIDE_NORTH,
Vector3::SIDE_SOUTH,

View File

@ -30,7 +30,9 @@ use pocketmine\permission\BanEntry;
use function array_map;
use function count;
use function implode;
use function sort;
use function strtolower;
use const SORT_STRING;
class BanListCommand extends VanillaCommand{
@ -62,10 +64,11 @@ class BanListCommand extends VanillaCommand{
$args[0] = "players";
}
$list = $list->getEntries();
$message = implode(", ", array_map(function(BanEntry $entry) : string{
$list = array_map(function(BanEntry $entry) : string{
return $entry->getName();
}, $list));
}, $list->getEntries());
sort($list, SORT_STRING);
$message = implode(", ", $list);
if($args[0] === "ips"){
$sender->sendMessage(new TranslationContainer("commands.banlist.ips", [count($list)]));

View File

@ -30,6 +30,8 @@ use function array_filter;
use function array_map;
use function count;
use function implode;
use function sort;
use const SORT_STRING;
class ListCommand extends VanillaCommand{
@ -52,6 +54,7 @@ class ListCommand extends VanillaCommand{
}, array_filter($sender->getServer()->getOnlinePlayers(), function(Player $player) use ($sender) : bool{
return $player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player));
}));
sort($playerNames, SORT_STRING);
$sender->sendMessage(new TranslationContainer("commands.players.list", [count($playerNames), $sender->getServer()->getMaxPlayers()]));
$sender->sendMessage(implode(", ", $playerNames));

View File

@ -30,6 +30,8 @@ use pocketmine\utils\TextFormat;
use function array_map;
use function count;
use function implode;
use function sort;
use const SORT_STRING;
class PluginsCommand extends VanillaCommand{
@ -51,6 +53,7 @@ class PluginsCommand extends VanillaCommand{
$list = array_map(function(Plugin $plugin) : string{
return ($plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED) . $plugin->getDescription()->getFullName();
}, $sender->getServer()->getPluginManager()->getPlugins());
sort($list, SORT_STRING);
$sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($list), implode(TextFormat::WHITE . ", ", $list)]));
return true;

View File

@ -27,11 +27,11 @@ use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\TranslationContainer;
use pocketmine\math\Vector3;
use pocketmine\level\Location;
use pocketmine\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
use function array_filter;
use function array_values;
use function array_shift;
use function count;
use function round;
@ -47,84 +47,83 @@ class TeleportCommand extends VanillaCommand{
$this->setPermission("pocketmine.command.teleport");
}
private function findPlayer(CommandSender $sender, string $playerName) : ?Player{
$subject = $sender->getServer()->getPlayer($playerName);
if($subject === null){
$sender->sendMessage(TextFormat::RED . "Can't find player " . $playerName);
return null;
}
return $subject;
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
if(!$this->testPermission($sender)){
return true;
}
$args = array_values(array_filter($args, function(string $arg) : bool{
return $arg !== "";
}));
if(count($args) < 1 or count($args) > 6){
throw new InvalidCommandSyntaxException();
}
$target = null;
$origin = $sender;
if(count($args) === 1 or count($args) === 3){
if($sender instanceof Player){
$target = $sender;
}else{
$sender->sendMessage(TextFormat::RED . "Please provide a player!");
return true;
}
if(count($args) === 1){
$target = $sender->getServer()->getPlayer($args[0]);
if($target === null){
$sender->sendMessage(TextFormat::RED . "Can't find player " . $args[0]);
switch(count($args)){
case 1: // /tp targetPlayer
case 3: // /tp x y z
case 5: // /tp x y z yaw pitch - TODO: 5 args could be target x y z yaw :(
if(!($sender instanceof Player)){
$sender->sendMessage(TextFormat::RED . "Please provide a player!");
return true;
}
}
}else{
$target = $sender->getServer()->getPlayer($args[0]);
if($target === null){
$sender->sendMessage(TextFormat::RED . "Can't find player " . $args[0]);
return true;
}
if(count($args) === 2){
$origin = $target;
$target = $sender->getServer()->getPlayer($args[1]);
if($target === null){
$sender->sendMessage(TextFormat::RED . "Can't find player " . $args[1]);
$subject = $sender;
$targetArgs = $args;
break;
case 2: // /tp player1 player2
case 4: // /tp player1 x y z - TODO: 4 args could be x y z yaw :(
case 6: // /tp player1 x y z yaw pitch
$subject = $this->findPlayer($sender, $args[0]);
if($subject === null){
return true;
}
}
$targetArgs = $args;
array_shift($targetArgs);
break;
default:
throw new InvalidCommandSyntaxException();
}
if(count($args) < 3){
$origin->teleport($target);
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success", [$origin->getName(), $target->getName()]));
switch(count($targetArgs)){
case 1:
$targetPlayer = $this->findPlayer($sender, $targetArgs[0]);
if($targetPlayer === null){
return true;
}
return true;
}elseif($target->isValid()){
if(count($args) === 4 or count($args) === 6){
$pos = 1;
}else{
$pos = 0;
}
$subject->teleport($targetPlayer->getLocation());
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success", [$subject->getName(), $targetPlayer->getName()]));
$x = $this->getRelativeDouble($target->x, $sender, $args[$pos++]);
$y = $this->getRelativeDouble($target->y, $sender, $args[$pos++], 0, 256);
$z = $this->getRelativeDouble($target->z, $sender, $args[$pos++]);
$yaw = $target->getYaw();
$pitch = $target->getPitch();
return true;
case 3:
case 5:
$base = $subject->getLocation();
if(count($targetArgs) === 5){
$yaw = (float) $targetArgs[3];
$pitch = (float) $targetArgs[4];
}else{
$yaw = $base->yaw;
$pitch = $base->pitch;
}
if(count($args) === 6 or (count($args) === 5 and $pos === 3)){
$yaw = (float) $args[$pos++];
$pitch = (float) $args[$pos++];
}
$x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]);
$y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], 0, 256);
$z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]);
$targetLocation = new Location($x, $y, $z, $yaw, $pitch, $base->getLevelNonNull());
$target->teleport(new Vector3($x, $y, $z), $yaw, $pitch);
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success.coordinates", [$target->getName(), round($x, 2), round($y, 2), round($z, 2)]));
return true;
$subject->teleport($targetLocation);
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success.coordinates", [
$subject->getName(),
round($targetLocation->x, 2),
round($targetLocation->y, 2),
round($targetLocation->z, 2)
]));
return true;
default:
throw new AssumptionFailedError("This branch should be unreachable (for now)");
}
throw new InvalidCommandSyntaxException();
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\TranslationContainer;
@ -72,12 +73,12 @@ class TimingsCommand extends VanillaCommand{
if($mode === "on"){
TimingsHandler::setEnabled();
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.enable"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.enable"));
return true;
}elseif($mode === "off"){
TimingsHandler::setEnabled(false);
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.disable"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.disable"));
return true;
}
@ -91,7 +92,7 @@ class TimingsCommand extends VanillaCommand{
if($mode === "reset"){
TimingsHandler::reload();
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.reset"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.reset"));
}elseif($mode === "merged" or $mode === "report" or $paste){
$timings = "";
if($paste){
@ -150,6 +151,7 @@ class TimingsCommand extends VanillaCommand{
}
public function onCompletion(Server $server){
/** @var CommandSender $sender */
$sender = $this->fetchLocal();
if($sender instanceof Player and !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
@ -159,18 +161,21 @@ class TimingsCommand extends VanillaCommand{
$server->getLogger()->logException($result);
return;
}
if(isset($result[0]) && is_array($response = json_decode($result[0], true)) && isset($response["id"])){
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead",
$response = json_decode($result[0], true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.timingsRead",
["https://" . $this->host . "/?id=" . $response["id"]]));
}else{
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError"));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.pasteError"));
}
}
});
}else{
fclose($fileTimings);
$sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings]));
}
}else{
throw new InvalidCommandSyntaxException();
}
return true;

View File

@ -31,7 +31,9 @@ use pocketmine\Player;
use pocketmine\utils\TextFormat;
use function count;
use function implode;
use function sort;
use function strtolower;
use const SORT_STRING;
class WhitelistCommand extends VanillaCommand{
@ -49,10 +51,6 @@ class WhitelistCommand extends VanillaCommand{
return true;
}
if(count($args) === 0 or count($args) > 2){
throw new InvalidCommandSyntaxException();
}
if(count($args) === 1){
if($this->badPerm($sender, strtolower($args[0]))){
return false;
@ -75,6 +73,7 @@ class WhitelistCommand extends VanillaCommand{
return true;
case "list":
$entries = $sender->getServer()->getWhitelisted()->getAll(true);
sort($entries, SORT_STRING);
$result = implode($entries, ", ");
$count = count($entries);
@ -112,7 +111,7 @@ class WhitelistCommand extends VanillaCommand{
}
}
return true;
throw new InvalidCommandSyntaxException();
}
private function badPerm(CommandSender $sender, string $subcommand) : bool{

View File

@ -45,6 +45,7 @@ class Attribute{
public const FALL_DAMAGE = 13;
public const HORSE_JUMP_STRENGTH = 14;
public const ZOMBIE_SPAWN_REINFORCEMENTS = 15;
public const LAVA_MOVEMENT = 16;
/** @var int */
private $id;
@ -84,6 +85,7 @@ class Attribute{
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);
self::addAttribute(self::LAVA_MOVEMENT, "minecraft:lava_movement", 0.0, 340282346638528859811704183484516925440.0, 0.02);
}
/**

View File

@ -31,7 +31,7 @@ use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\utils\Color;
use function constant;
use function defined;
use function strtoupper;
use function mb_strtoupper;
class Effect{
public const SPEED = 1;
@ -102,7 +102,7 @@ class Effect{
}
public static function getEffectByName(string $name) : ?Effect{
$const = self::class . "::" . strtoupper($name);
$const = self::class . "::" . mb_strtoupper($name);
if(defined($const)){
return self::getEffect(constant($const));
}

View File

@ -311,12 +311,15 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_ROARING = 83;
public const DATA_FLAG_DELAYED_ATTACKING = 84;
public const DATA_FLAG_AVOIDING_MOBS = 85;
public const DATA_FLAG_FACING_TARGET_TO_RANGE_ATTACK = 86;
public const DATA_FLAG_HIDDEN_WHEN_INVISIBLE = 87; //??????????????????
public const DATA_FLAG_IS_IN_UI = 88;
public const DATA_FLAG_STALKING = 89;
public const DATA_FLAG_EMOTING = 90;
public const DATA_FLAG_CELEBRATING = 91;
public const DATA_FLAG_AVOIDING_BLOCK = 86;
public const DATA_FLAG_FACING_TARGET_TO_RANGE_ATTACK = 87;
public const DATA_FLAG_HIDDEN_WHEN_INVISIBLE = 88; //??????????????????
public const DATA_FLAG_IS_IN_UI = 89;
public const DATA_FLAG_STALKING = 90;
public const DATA_FLAG_EMOTING = 91;
public const DATA_FLAG_CELEBRATING = 92;
public const DATA_FLAG_ADMIRING = 93;
public const DATA_FLAG_CELEBRATING_SPECIAL = 94;
public const DATA_PLAYER_FLAG_SLEEP = 1;
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK

View File

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

View File

@ -62,24 +62,24 @@ class Skin{
try{
$this->validate();
return true;
}catch(\InvalidArgumentException $e){
}catch(InvalidSkinException $e){
return false;
}
}
/**
* @throws \InvalidArgumentException
* @throws InvalidSkinException
*/
public function validate() : void{
if($this->skinId === ""){
throw new \InvalidArgumentException("Skin ID must not be empty");
throw new InvalidSkinException("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) . ")");
throw new InvalidSkinException("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)");
throw new InvalidSkinException("Invalid cape data size " . strlen($this->capeData) . " bytes (must be exactly 8192 bytes)");
}
//TODO: validate geometry
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\event;
use function constant;
use function defined;
use function strtoupper;
use function mb_strtoupper;
/**
* List of event priorities
@ -79,7 +79,7 @@ abstract class EventPriority{
* @throws \InvalidArgumentException
*/
public static function fromString(string $name) : int{
$name = strtoupper($name);
$name = mb_strtoupper($name);
$const = self::class . "::" . $name;
if($name !== "ALL" and defined($const)){
return constant($const);

View File

@ -26,6 +26,7 @@ namespace pocketmine\event\block;
use pocketmine\block\Block;
use pocketmine\event\Cancellable;
use pocketmine\Player;
use pocketmine\utils\Utils;
use function count;
/**
@ -79,6 +80,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
if(count($lines) !== 4){
throw new \InvalidArgumentException("Array size must be 4!");
}
Utils::validateArrayValueType($lines, function(string $_) : void{});
$this->lines = $lines;
}

View File

@ -29,6 +29,7 @@ use pocketmine\event\Cancellable;
/**
* Called when an Entity, excluding players, changes a block directly
* @phpstan-extends EntityEvent<Entity>
*/
class EntityBlockChangeEvent extends EntityEvent implements Cancellable{
/** @var Block */

View File

@ -26,6 +26,9 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityCombustEvent extends EntityEvent implements Cancellable{
/** @var int */
protected $duration;

View File

@ -30,6 +30,7 @@ use function max;
/**
* Called when an entity takes damage.
* @phpstan-extends EntityEvent<Entity>
*/
class EntityDamageEvent extends EntityEvent implements Cancellable{
public const MODIFIER_ARMOR = 1;

View File

@ -25,7 +25,11 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\utils\Utils;
/**
* @phpstan-extends EntityEvent<Living>
*/
class EntityDeathEvent extends EntityEvent{
/** @var Item[] */
private $drops = [];
@ -59,6 +63,7 @@ class EntityDeathEvent extends EntityEvent{
* @param Item[] $drops
*/
public function setDrops(array $drops) : void{
Utils::validateArrayValueType($drops, function(Item $_) : void{});
$this->drops = $drops;
}

View File

@ -32,6 +32,7 @@ use pocketmine\entity\Vehicle;
/**
* Called when a entity is despawned
* @phpstan-extends EntityEvent<Entity>
*/
class EntityDespawnEvent extends EntityEvent{
/** @var int */

View File

@ -27,6 +27,9 @@ use pocketmine\entity\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityEffectEvent extends EntityEvent implements Cancellable{
/** @var EffectInstance */
private $effect;

View File

@ -29,12 +29,19 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Entity;
use pocketmine\event\Event;
/**
* @phpstan-template TEntity of Entity
*/
abstract class EntityEvent extends Event{
/** @var Entity */
/**
* @var Entity
* @phpstan-var TEntity
*/
protected $entity;
/**
* @return Entity
* @phpstan-return TEntity
*/
public function getEntity(){
return $this->entity;

View File

@ -27,9 +27,11 @@ use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\level\Position;
use pocketmine\utils\Utils;
/**
* Called when a entity explodes
* @phpstan-extends EntityEvent<Entity>
*/
class EntityExplodeEvent extends EntityEvent implements Cancellable{
/** @var Position */
@ -66,6 +68,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
* @param Block[] $blocks
*/
public function setBlockList(array $blocks) : void{
Utils::validateArrayValueType($blocks, function(Block $_) : void{});
$this->blocks = $blocks;
}

View File

@ -29,6 +29,7 @@ use pocketmine\item\Item;
/**
* Called before a slot in an entity's inventory changes.
* @phpstan-extends EntityEvent<Entity>
*/
class EntityInventoryChangeEvent extends EntityEvent implements Cancellable{
/** @var Item */

View File

@ -27,6 +27,9 @@ use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\level\Level;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityLevelChangeEvent extends EntityEvent implements Cancellable{
/** @var Level */
private $originLevel;

View File

@ -27,6 +27,9 @@ use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\math\Vector3;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityMotionEvent extends EntityEvent implements Cancellable{
/** @var Vector3 */
private $mot;

View File

@ -26,6 +26,9 @@ namespace pocketmine\event\entity;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityRegainHealthEvent extends EntityEvent implements Cancellable{
public const CAUSE_REGEN = 0;
public const CAUSE_EATING = 1;

View File

@ -30,6 +30,9 @@ use pocketmine\event\Cancellable;
use pocketmine\item\Item;
use function count;
/**
* @phpstan-extends EntityEvent<Living>
*/
class EntityShootBowEvent extends EntityEvent implements Cancellable{
/** @var Item */
private $bow;

View File

@ -33,6 +33,7 @@ use pocketmine\level\Position;
/**
* Called when a entity is spawned
* @phpstan-extends EntityEvent<Entity>
*/
class EntitySpawnEvent extends EntityEvent{
/** @var int */

View File

@ -27,6 +27,9 @@ use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\level\Position;
/**
* @phpstan-extends EntityEvent<Entity>
*/
class EntityTeleportEvent extends EntityEvent implements Cancellable{
/** @var Position */
private $from;

View File

@ -28,6 +28,7 @@ use pocketmine\event\Cancellable;
/**
* Called when a entity decides to explode
* @phpstan-extends EntityEvent<Entity>
*/
class ExplosionPrimeEvent extends EntityEvent implements Cancellable{
/** @var float */

View File

@ -26,6 +26,9 @@ namespace pocketmine\event\entity;
use pocketmine\entity\object\ItemEntity;
use pocketmine\event\Cancellable;
/**
* @phpstan-extends EntityEvent<ItemEntity>
*/
class ItemDespawnEvent extends EntityEvent implements Cancellable{
public function __construct(ItemEntity $item){

View File

@ -25,6 +25,9 @@ namespace pocketmine\event\entity;
use pocketmine\entity\object\ItemEntity;
/**
* @phpstan-extends EntityEvent<ItemEntity>
*/
class ItemSpawnEvent extends EntityEvent{
public function __construct(ItemEntity $item){

View File

@ -28,6 +28,7 @@ use pocketmine\math\RayTraceResult;
/**
* @allowHandle
* @phpstan-extends EntityEvent<Projectile>
*/
abstract class ProjectileHitEvent extends EntityEvent{
/** @var RayTraceResult */

View File

@ -26,6 +26,9 @@ namespace pocketmine\event\entity;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\Cancellable;
/**
* @phpstan-extends EntityEvent<Projectile>
*/
class ProjectileLaunchEvent extends EntityEvent implements Cancellable{
public function __construct(Projectile $entity){
$this->entity = $entity;

View File

@ -28,6 +28,8 @@ use pocketmine\event\Cancellable;
use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\utils\Utils;
use function array_values;
use function spl_object_id;
/**
@ -97,6 +99,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
* @param CommandSender[] $recipients
*/
public function setRecipients(array $recipients) : void{
Utils::validateArrayValueType($recipients, function(CommandSender $_) : void{});
$this->recipients = $recipients;
}
}

View File

@ -27,6 +27,9 @@ use pocketmine\entity\Human;
use pocketmine\event\Cancellable;
use pocketmine\event\entity\EntityEvent;
/**
* @phpstan-extends EntityEvent<Human>
*/
class PlayerExhaustEvent extends EntityEvent implements Cancellable{
public const CAUSE_ATTACK = 1;
public const CAUSE_DAMAGE = 2;

View File

@ -29,6 +29,7 @@ use pocketmine\event\entity\EntityEvent;
/**
* Called when a player gains or loses XP levels and/or progress.
* @phpstan-extends EntityEvent<Human>
*/
class PlayerExperienceChangeEvent extends EntityEvent implements Cancellable{
/** @var Human */

View File

@ -43,6 +43,9 @@ class PlayerRespawnEvent extends PlayerEvent{
}
public function setRespawnPosition(Position $position) : void{
if(!$position->isValid()){
throw new \InvalidArgumentException("Spawn position must reference a valid and loaded World");
}
$this->position = $position;
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use function chr;
use function count;
use function str_replace;
@ -76,7 +77,7 @@ class QueryRegenerateEvent extends ServerEvent{
public function __construct(Server $server){
$this->serverName = $server->getMotd();
$this->listPlugins = $server->getProperty("settings.query-plugins", true);
$this->listPlugins = (bool) $server->getProperty("settings.query-plugins", true);
$this->plugins = $server->getPluginManager()->getPlugins();
$this->players = [];
foreach($server->getOnlinePlayers() as $player){
@ -145,6 +146,7 @@ class QueryRegenerateEvent extends ServerEvent{
* @param Plugin[] $plugins
*/
public function setPlugins(array $plugins) : void{
Utils::validateArrayValueType($plugins, function(Plugin $_) : void{});
$this->plugins = $plugins;
$this->destroyCache();
}
@ -160,6 +162,7 @@ class QueryRegenerateEvent extends ServerEvent{
* @param Player[] $players
*/
public function setPlayerList(array $players) : void{
Utils::validateArrayValueType($players, function(Player $_) : void{});
$this->players = $players;
$this->destroyCache();
}

View File

@ -28,7 +28,9 @@ use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\Player;
use function array_map;
use function array_merge;
class ArmorInventory extends BaseInventory{
@ -109,7 +111,7 @@ class ArmorInventory extends BaseInventory{
$pk2 = new InventorySlotPacket();
$pk2->windowId = $player->getWindowId($this);
$pk2->inventorySlot = $index;
$pk2->item = $this->getItem($index);
$pk2->item = ItemStackWrapper::legacy($this->getItem($index));
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);
@ -134,7 +136,7 @@ class ArmorInventory extends BaseInventory{
if($player === $this->getHolder()){
$pk2 = new InventoryContentPacket();
$pk2->windowId = $player->getWindowId($this);
$pk2->items = $this->getContents(true);
$pk2->items = array_map([ItemStackWrapper::class, 'legacy'], $this->getContents(true));
$player->dataPacket($pk2);
}else{
$player->dataPacket($pk);

View File

@ -31,7 +31,9 @@ use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\Player;
use function array_map;
use function array_slice;
use function count;
use function max;
@ -53,7 +55,7 @@ abstract class BaseInventory implements Inventory{
protected $slots;
/** @var Player[] */
protected $viewers = [];
/** @var InventoryEventProcessor */
/** @var InventoryEventProcessor|null */
protected $eventProcessor;
/**
@ -433,7 +435,7 @@ abstract class BaseInventory implements Inventory{
}
$pk = new InventoryContentPacket();
$pk->items = $this->getContents(true);
$pk->items = array_map([ItemStackWrapper::class, 'legacy'], $this->getContents(true));
foreach($target as $player){
if(($id = $player->getWindowId($this)) === ContainerIds::NONE){
@ -455,7 +457,7 @@ abstract class BaseInventory implements Inventory{
$pk = new InventorySlotPacket();
$pk->inventorySlot = $index;
$pk->item = $this->getItem($index);
$pk->item = ItemStackWrapper::legacy($this->getItem($index));
foreach($target as $player){
if(($id = $player->getWindowId($this)) === ContainerIds::NONE){

View File

@ -26,10 +26,12 @@ namespace pocketmine\inventory;
use pocketmine\entity\Human;
use pocketmine\event\player\PlayerItemHeldEvent;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use pocketmine\Player;
use function array_map;
use function in_array;
use function is_array;
@ -196,16 +198,11 @@ class PlayerInventory extends BaseInventory{
if(!($holder instanceof Player)){
throw new \LogicException("Cannot send creative inventory contents to non-player inventory holder");
}
$pk = new InventoryContentPacket();
$pk->windowId = ContainerIds::CREATIVE;
if(!$holder->isSpectator()){ //fill it for all gamemodes except spectator
foreach(Item::getCreativeItems() as $i => $item){
$pk->items[$i] = clone $item;
}
}
$holder->dataPacket($pk);
$nextEntryId = 1;
$holder->sendDataPacket(CreativeContentPacket::create(array_map(function(Item $item) use (&$nextEntryId) : CreativeContentEntry{
return new CreativeContentEntry($nextEntryId++, clone $item);
}, $holder->isSpectator() ? [] : Item::getCreativeItems()))); //fill it for all gamemodes except spectator
}
/**

View File

@ -164,7 +164,7 @@ class ShapedRecipe implements CraftingRecipe{
}
public function getIngredient(int $x, int $y) : Item{
$exists = $this->ingredientList[$this->shape[$y]{$x}] ?? null;
$exists = $this->ingredientList[$this->shape[$y][$x]] ?? null;
return $exists !== null ? clone $exists : ItemFactory::get(Item::AIR, 0, 0);
}

View File

@ -27,7 +27,7 @@ use pocketmine\event\inventory\CraftItemEvent;
use pocketmine\inventory\CraftingRecipe;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\Player;
use function array_pop;
use function count;
use function intdiv;
@ -165,7 +165,7 @@ class CraftingTransaction extends InventoryTransaction{
* transaction goes wrong.
*/
$pk = new ContainerClosePacket();
$pk->windowId = ContainerIds::NONE;
$pk->windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID;
$this->source->dataPacket($pk);
}

View File

@ -27,7 +27,6 @@ use pocketmine\block\Liquid;
use pocketmine\entity\Living;
use pocketmine\level\sound\EndermanTeleportSound;
use pocketmine\math\Vector3;
use function assert;
use function min;
use function mt_rand;

View File

@ -34,8 +34,8 @@ use function gettype;
use function is_numeric;
use function is_object;
use function is_string;
use function mb_strtoupper;
use function str_replace;
use function strtoupper;
use function trim;
/**
@ -375,8 +375,8 @@ class ItemFactory{
if(is_numeric($b[0])){
$item = self::get((int) $b[0], $meta);
}elseif(defined(ItemIds::class . "::" . strtoupper($b[0]))){
$item = self::get(constant(ItemIds::class . "::" . strtoupper($b[0])), $meta);
}elseif(defined(ItemIds::class . "::" . mb_strtoupper($b[0]))){
$item = self::get(constant(ItemIds::class . "::" . mb_strtoupper($b[0])), $meta);
}else{
throw new \InvalidArgumentException("Unable to resolve \"" . $str . "\" to a valid item");
}

View File

@ -26,7 +26,7 @@ namespace pocketmine\item\enchantment;
use pocketmine\event\entity\EntityDamageEvent;
use function constant;
use function defined;
use function strtoupper;
use function mb_strtoupper;
/**
* Manages enchantment type data.
@ -69,6 +69,7 @@ class Enchantment{
public const MULTISHOT = 33;
public const PIERCING = 34;
public const QUICK_CHARGE = 35;
public const SOUL_SPEED = 36;
public const RARITY_COMMON = 10;
public const RARITY_UNCOMMON = 5;
@ -161,7 +162,7 @@ class Enchantment{
}
public static function getEnchantmentByName(string $name) : ?Enchantment{
$const = Enchantment::class . "::" . strtoupper($name);
$const = Enchantment::class . "::" . mb_strtoupper($name);
if(defined($const)){
return self::getEnchantment(constant($const));
}

View File

@ -131,7 +131,7 @@ class BaseLang{
$baseText = $this->parseTranslation(($onlyPrefix === null or strpos($str, $onlyPrefix) === 0) ? $baseText : $str, $onlyPrefix);
foreach($params as $i => $p){
$baseText = str_replace("{%$i}", $this->parseTranslation((string) $p), $baseText, $onlyPrefix);
$baseText = str_replace("{%$i}", $this->parseTranslation((string) $p), $baseText);
}
return $baseText;

View File

@ -132,8 +132,11 @@ class Explosion{
if($blockId !== 0){
$blastForce -= (BlockFactory::$blastResistance[$blockId] / 5 + 0.3) * $this->stepLen;
if($blastForce > 0){
if(!isset($this->affectedBlocks[$index = Level::blockHash($vBlock->x, $vBlock->y, $vBlock->z)])){
$this->affectedBlocks[$index] = BlockFactory::get($blockId, $this->subChunkHandler->currentSubChunk->getBlockData($vBlock->x & 0x0f, $vBlock->y & 0x0f, $vBlock->z & 0x0f), $vBlock);
if(!isset($this->affectedBlocks[Level::blockHash($vBlock->x, $vBlock->y, $vBlock->z)])){
$_block = BlockFactory::get($blockId, $this->subChunkHandler->currentSubChunk->getBlockData($vBlock->x & 0x0f, $vBlock->y & 0x0f, $vBlock->z & 0x0f), $vBlock);
foreach($_block->getAffectedBlocks() as $_affectedBlock){
$this->affectedBlocks[Level::blockHash($_affectedBlock->x, $_affectedBlock->y, $_affectedBlock->z)] = $_affectedBlock;
}
}
}
}

View File

@ -72,6 +72,7 @@ use pocketmine\metadata\Metadatable;
use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\AddActorPacket;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
@ -79,7 +80,6 @@ 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;
@ -216,12 +216,18 @@ class Level implements ChunkManager, Metadatable{
/** @var Vector3[][] */
private $changedBlocks = [];
/** @var ReversePriorityQueue */
/**
* @var ReversePriorityQueue
* @phpstan-var ReversePriorityQueue<int, Vector3>
*/
private $scheduledBlockUpdateQueue;
/** @var int[] */
private $scheduledBlockUpdateQueueIndex = [];
/** @var \SplQueue */
/**
* @var \SplQueue
* @phpstan-var \SplQueue<int>
*/
private $neighbourBlockUpdateQueue;
/** @var Player[][] */

View File

@ -24,8 +24,8 @@ declare(strict_types=1);
namespace pocketmine\level;
use pocketmine\math\Vector3;
use pocketmine\utils\MainLogger;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\MainLogger;
use function assert;
class Position extends Vector3{

View File

@ -109,6 +109,7 @@ class Chunk{
* @param CompoundTag[] $entities
* @param CompoundTag[] $tiles
* @param int[] $heightMap
* @phpstan-param list<int> $heightMap
*/
public function __construct(int $chunkX, int $chunkZ, array $subChunks = [], array $entities = [], array $tiles = [], string $biomeIds = "", array $heightMap = []){
$this->x = $chunkX;

View File

@ -98,6 +98,7 @@ if(!extension_loaded('pocketmine_chunkutils')){
* Converts pre-MCPE-1.0 biome color array to biome ID array.
*
* @param int[] $array of biome color values
* @phpstan-param list<int> $array
*/
public static function convertBiomeColors(array $array) : string{
$result = str_repeat("\x00", 256);

View File

@ -301,7 +301,8 @@ class LevelDB extends BaseLevelProvider{
protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
if(!$this->chunkExists($chunkX, $chunkZ)){
$chunkVersionRaw = $this->db->get($index . self::TAG_VERSION);
if($chunkVersionRaw === false){
return null;
}
@ -316,7 +317,7 @@ class LevelDB extends BaseLevelProvider{
/** @var bool $lightPopulated */
$lightPopulated = true;
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
$chunkVersion = ord($chunkVersionRaw);
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
$binaryStream = new BinaryStream();
@ -368,7 +369,11 @@ class LevelDB extends BaseLevelProvider{
}
break;
case 2: // < MCPE 1.0
$binaryStream->setBuffer($this->db->get($index . self::TAG_LEGACY_TERRAIN));
$legacyTerrain = $this->db->get($index . self::TAG_LEGACY_TERRAIN);
if($legacyTerrain === false){
throw new CorruptedChunkException("Expected to find a LEGACY_TERRAIN key for this chunk version, but none found");
}
$binaryStream->setBuffer($legacyTerrain);
$fullIds = $binaryStream->get(32768);
$fullData = $binaryStream->get(16384);
$fullSkyLight = $binaryStream->get(16384);

View File

@ -0,0 +1,154 @@
<?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\level\format\io\region;
use pocketmine\utils\AssumptionFailedError;
use function end;
use function ksort;
use function time;
use const SORT_NUMERIC;
final class RegionGarbageMap{
/** @var RegionLocationTableEntry[] */
private $entries = [];
/** @var bool */
private $clean = false;
/**
* @param RegionLocationTableEntry[] $entries
*/
public function __construct(array $entries){
foreach($entries as $entry){
$this->entries[$entry->getFirstSector()] = $entry;
}
}
/**
* @param RegionLocationTableEntry[]|null[] $locationTable
*/
public static function buildFromLocationTable(array $locationTable) : self{
/** @var RegionLocationTableEntry[] $usedMap */
$usedMap = [];
foreach($locationTable as $entry){
if($entry === null){
continue;
}
if(isset($usedMap[$entry->getFirstSector()])){
throw new AssumptionFailedError("Overlapping entries detected");
}
$usedMap[$entry->getFirstSector()] = $entry;
}
ksort($usedMap, SORT_NUMERIC);
/** @var RegionLocationTableEntry[] $garbageMap */
$garbageMap = [];
/** @var RegionLocationTableEntry|null $prevEntry */
$prevEntry = null;
foreach($usedMap as $firstSector => $entry){
$expectedStart = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
$actualStart = $entry->getFirstSector();
if($expectedStart < $actualStart){
//found a gap in the table
$garbageMap[$expectedStart] = new RegionLocationTableEntry($expectedStart, $actualStart - $expectedStart, 0);
}
$prevEntry = $entry;
}
return new self($garbageMap);
}
/**
* @return RegionLocationTableEntry[]
* @phpstan-return array<int, RegionLocationTableEntry>
*/
public function getArray() : array{
if(!$this->clean){
ksort($this->entries, SORT_NUMERIC);
/** @var int|null $prevIndex */
$prevIndex = null;
foreach($this->entries as $k => $entry){
if($prevIndex !== null and $this->entries[$prevIndex]->getLastSector() + 1 === $entry->getFirstSector()){
//this SHOULD overwrite the previous index and not appear at the end
$this->entries[$prevIndex] = new RegionLocationTableEntry(
$this->entries[$prevIndex]->getFirstSector(),
$this->entries[$prevIndex]->getSectorCount() + $entry->getSectorCount(),
0
);
unset($this->entries[$k]);
}else{
$prevIndex = $k;
}
}
$this->clean = true;
}
return $this->entries;
}
public function add(RegionLocationTableEntry $entry) : void{
if(isset($this->entries[$k = $entry->getFirstSector()])){
throw new \InvalidArgumentException("Overlapping entry starting at " . $k);
}
$this->entries[$k] = $entry;
$this->clean = false;
}
public function remove(RegionLocationTableEntry $entry) : void{
if(isset($this->entries[$k = $entry->getFirstSector()])){
//removal doesn't affect ordering and shouldn't affect fragmentation
unset($this->entries[$k]);
}
}
public function end() : ?RegionLocationTableEntry{
$array = $this->getArray();
$end = end($array);
return $end !== false ? $end : null;
}
public function allocate(int $newSize) : ?RegionLocationTableEntry{
foreach($this->getArray() as $start => $candidate){
$candidateSize = $candidate->getSectorCount();
if($candidateSize < $newSize){
continue;
}
$newLocation = new RegionLocationTableEntry($candidate->getFirstSector(), $newSize, time());
$this->remove($candidate);
if($candidateSize > $newSize){ //we're not using the whole area, just take part of it
$newGarbageStart = $candidate->getFirstSector() + $newSize;
$newGarbageSize = $candidateSize - $newSize;
$this->add(new RegionLocationTableEntry($newGarbageStart, $newGarbageSize, 0));
}
return $newLocation;
}
return null;
}
}

View File

@ -28,6 +28,7 @@ use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger;
use function assert;
use function ceil;
use function chr;
use function fclose;
@ -40,10 +41,12 @@ use function fseek;
use function ftruncate;
use function fwrite;
use function is_resource;
use function ksort;
use function max;
use function ord;
use function pack;
use function str_pad;
use function str_repeat;
use function stream_set_read_buffer;
use function stream_set_write_buffer;
use function strlen;
@ -51,6 +54,7 @@ use function substr;
use function time;
use function touch;
use function unpack;
use const SORT_NUMERIC;
use const STR_PAD_RIGHT;
class RegionLoader{
@ -61,7 +65,7 @@ class RegionLoader{
public const MAX_SECTOR_LENGTH = 255 << 12; //255 sectors (~0.996 MiB)
public const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
private const FIRST_SECTOR = 2; //location table occupies 0 and 1
public const FIRST_SECTOR = 2; //location table occupies 0 and 1
/** @var int */
public static $COMPRESSION_LEVEL = 7;
@ -76,8 +80,10 @@ class RegionLoader{
protected $filePointer;
/** @var int */
protected $nextSector = self::FIRST_SECTOR;
/** @var RegionLocationTableEntry[] */
/** @var RegionLocationTableEntry[]|null[] */
protected $locationTable = [];
/** @var RegionGarbageMap */
protected $garbageTable;
/** @var int */
public $lastUsed = 0;
@ -85,6 +91,7 @@ class RegionLoader{
$this->x = $regionX;
$this->z = $regionZ;
$this->filePath = $filePath;
$this->garbageTable = new RegionGarbageMap([]);
}
/**
@ -121,7 +128,7 @@ class RegionLoader{
}
protected function isChunkGenerated(int $index) : bool{
return !$this->locationTable[$index]->isNull();
return $this->locationTable[$index] !== null;
}
/**
@ -133,7 +140,7 @@ class RegionLoader{
$this->lastUsed = time();
if(!$this->isChunkGenerated($index)){
if($this->locationTable[$index] === null){
return null;
}
@ -194,19 +201,50 @@ class RegionLoader{
$newSize = (int) ceil(($length + 4) / 4096);
$index = self::getChunkOffset($x, $z);
$offset = $this->locationTable[$index]->getFirstSector();
if($this->locationTable[$index]->getSectorCount() < $newSize){
$offset = $this->nextSector;
/*
* look for an unused area big enough to hold this data
* this is corruption-resistant (it leaves the old data intact if a failure occurs when writing new data), and
* also allows the file to become more compact across consecutive writes without introducing a dedicated garbage
* collection mechanism.
*/
$newLocation = $this->garbageTable->allocate($newSize);
/* if no gaps big enough were found, append to the end of the file instead */
if($newLocation === null){
$newLocation = new RegionLocationTableEntry($this->nextSector, $newSize, time());
$this->bumpNextFreeSector($newLocation);
}
$this->locationTable[$index] = new RegionLocationTableEntry($offset, $newSize, time());
$this->bumpNextFreeSector($this->locationTable[$index]);
fseek($this->filePointer, $offset << 12);
/* write the chunk data into the chosen location */
fseek($this->filePointer, $newLocation->getFirstSector() << 12);
fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT));
/*
* update the file header - we do this after writing the main data, so that if a failure occurs while writing,
* the header will still point to the old (intact) copy of the chunk, instead of a potentially broken new
* version of the file (e.g. partially written).
*/
$oldLocation = $this->locationTable[$index];
$this->locationTable[$index] = $newLocation;
$this->writeLocationIndex($index);
if($oldLocation !== null){
/* release the area containing the old copy to the garbage pool */
$this->garbageTable->add($oldLocation);
$endGarbage = $this->garbageTable->end();
$nextSector = $this->nextSector;
for(; $endGarbage !== null and $endGarbage->getLastSector() + 1 === $nextSector; $endGarbage = $this->garbageTable->end()){
$nextSector = $endGarbage->getFirstSector();
$this->garbageTable->remove($endGarbage);
}
if($nextSector !== $this->nextSector){
$this->nextSector = $nextSector;
ftruncate($this->filePointer, $this->nextSector << 12);
}
}
}
/**
@ -215,7 +253,7 @@ class RegionLoader{
*/
public function removeChunk(int $x, int $z){
$index = self::getChunkOffset($x, $z);
$this->locationTable[$index] = new RegionLocationTableEntry(0, 0, 0);
$this->locationTable[$index] = null;
$this->writeLocationIndex($index);
}
@ -261,8 +299,8 @@ class RegionLoader{
fseek($this->filePointer, 0);
$headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH);
if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){
throw new CorruptedRegionException("Invalid region file header, expected " . self::REGION_HEADER_LENGTH . " bytes, got " . $len . " bytes");
if($headerRaw === false or strlen($headerRaw) !== self::REGION_HEADER_LENGTH){
throw new CorruptedRegionException("Corrupted region header (unexpected end of file)");
}
$data = unpack("N*", $headerRaw);
@ -270,18 +308,23 @@ class RegionLoader{
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
$sectorCount = $index & 0xff;
$timestamp = $data[$i + 1025];
if($offset === 0){
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
if($offset === 0 or $sectorCount === 0){
$this->locationTable[$i] = null;
}elseif($offset >= self::FIRST_SECTOR){
$this->bumpNextFreeSector($this->locationTable[$i] = new RegionLocationTableEntry($offset, $sectorCount, $timestamp));
}else{
$this->locationTable[$i] = new RegionLocationTableEntry($offset, $index & 0xff, $timestamp);
$this->bumpNextFreeSector($this->locationTable[$i]);
self::getChunkCoords($i, $chunkXX, $chunkZZ);
throw new CorruptedRegionException("Invalid region header entry for x=$chunkXX z=$chunkZZ, offset overlaps with header");
}
}
$this->checkLocationTableValidity();
$this->garbageTable = RegionGarbageMap::buildFromLocationTable($this->locationTable);
fseek($this->filePointer, 0);
}
@ -294,7 +337,7 @@ class RegionLoader{
for($i = 0; $i < 1024; ++$i){
$entry = $this->locationTable[$i];
if($entry->isNull()){
if($entry === null){
continue;
}
@ -314,16 +357,34 @@ class RegionLoader{
}
$usedOffsets[$offset] = $i;
}
ksort($usedOffsets, SORT_NUMERIC);
$prevLocationIndex = null;
foreach($usedOffsets as $startOffset => $locationTableIndex){
if($this->locationTable[$locationTableIndex] === null){
continue;
}
if($prevLocationIndex !== null){
assert($this->locationTable[$prevLocationIndex] !== null);
if($this->locationTable[$locationTableIndex]->overlaps($this->locationTable[$prevLocationIndex])){
self::getChunkCoords($locationTableIndex, $chunkXX, $chunkZZ);
self::getChunkCoords($prevLocationIndex, $prevChunkXX, $prevChunkZZ);
throw new CorruptedRegionException("Overlapping chunks detected in region header (chunk1: x=$chunkXX,z=$chunkZZ, chunk2: x=$prevChunkXX,z=$prevChunkZZ)");
}
}
$prevLocationIndex = $locationTableIndex;
}
}
private function writeLocationTable() : void{
$write = [];
for($i = 0; $i < 1024; ++$i){
$write[] = (($this->locationTable[$i]->getFirstSector() << 8) | $this->locationTable[$i]->getSectorCount());
$entry = $this->locationTable[$i];
$write[] = $entry !== null ? (($entry->getFirstSector() << 8) | $entry->getSectorCount()) : 0;
}
for($i = 0; $i < 1024; ++$i){
$write[] = $this->locationTable[$i]->getTimestamp();
$entry = $this->locationTable[$i];
$write[] = $entry !== null ? $entry->getTimestamp() : 0;
}
fseek($this->filePointer, 0);
fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2);
@ -335,10 +396,11 @@ class RegionLoader{
* @return void
*/
protected function writeLocationIndex($index){
$entry = $this->locationTable[$index];
fseek($this->filePointer, $index << 2);
fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index]->getFirstSector() << 8) | $this->locationTable[$index]->getSectorCount()), 4);
fwrite($this->filePointer, Binary::writeInt($entry !== null ? ($entry->getFirstSector() << 8) | $entry->getSectorCount() : 0), 4);
fseek($this->filePointer, 4096 + ($index << 2));
fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index]->getTimestamp()), 4);
fwrite($this->filePointer, Binary::writeInt($entry !== null ? $entry->getTimestamp() : 0), 4);
}
/**
@ -348,12 +410,48 @@ class RegionLoader{
fseek($this->filePointer, 0);
ftruncate($this->filePointer, 8192); // this fills the file with the null byte
for($i = 0; $i < 1024; ++$i){
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
$this->locationTable[$i] = null;
}
}
private function bumpNextFreeSector(RegionLocationTableEntry $entry) : void{
$this->nextSector = max($this->nextSector, $entry->getLastSector()) + 1;
$this->nextSector = max($this->nextSector, $entry->getLastSector() + 1);
}
public function generateSectorMap(string $usedChar, string $freeChar) : string{
$result = str_repeat($freeChar, $this->nextSector);
for($i = 0; $i < self::FIRST_SECTOR; ++$i){
$result[$i] = $usedChar;
}
foreach($this->locationTable as $locationTableEntry){
if($locationTableEntry === null){
continue;
}
foreach($locationTableEntry->getUsedSectors() as $sectorIndex){
if($sectorIndex >= strlen($result)){
throw new AssumptionFailedError("This should never happen...");
}
if($result[$sectorIndex] === $usedChar){
throw new AssumptionFailedError("Overlap detected");
}
$result[$sectorIndex] = $usedChar;
}
}
return $result;
}
/**
* Returns a float between 0 and 1 indicating what fraction of the file is currently unused space.
*/
public function getProportionUnusedSpace() : float{
$size = $this->nextSector;
$used = self::FIRST_SECTOR; //header is always allocated
foreach($this->locationTable as $entry){
if($entry !== null){
$used += $entry->getSectorCount();
}
}
return 1 - ($used / $size);
}
public function getX() : int{

View File

@ -38,12 +38,12 @@ class RegionLocationTableEntry{
* @throws \InvalidArgumentException
*/
public function __construct(int $firstSector, int $sectorCount, int $timestamp){
if($firstSector < 0){
if($firstSector < 0 or $firstSector >= 2 ** 24){
throw new \InvalidArgumentException("Start sector must be positive, got $firstSector");
}
$this->firstSector = $firstSector;
if($sectorCount < 0 or $sectorCount > 255){
throw new \InvalidArgumentException("Sector count must be in range 0...255, got $sectorCount");
if($sectorCount < 1){
throw new \InvalidArgumentException("Sector count must be positive, got $sectorCount");
}
$this->sectorCount = $sectorCount;
$this->timestamp = $timestamp;
@ -73,7 +73,16 @@ class RegionLocationTableEntry{
return $this->timestamp;
}
public function isNull() : bool{
return $this->firstSector === 0 or $this->sectorCount === 0;
public function overlaps(RegionLocationTableEntry $other) : bool{
$overlapCheck = static function(RegionLocationTableEntry $entry1, RegionLocationTableEntry $entry2) : bool{
$entry1Last = $entry1->getLastSector();
$entry2Last = $entry2->getLastSector();
return (
($entry2->firstSector >= $entry1->firstSector and $entry2->firstSector <= $entry1Last) or
($entry2Last >= $entry1->firstSector and $entry2Last <= $entry1Last)
);
};
return $overlapCheck($this, $other) or $overlapCheck($other, $this);
}
}

View File

@ -40,7 +40,10 @@ abstract class LightUpdate{
*/
protected $updateNodes = [];
/** @var \SplQueue */
/**
* @var \SplQueue
* @phpstan-var \SplQueue<array{int, int, int}>
*/
protected $spreadQueue;
/**
* @var true[]
@ -48,7 +51,10 @@ abstract class LightUpdate{
*/
protected $spreadVisited = [];
/** @var \SplQueue */
/**
* @var \SplQueue
* @phpstan-var \SplQueue<array{int, int, int, int}>
*/
protected $removalQueue;
/**
* @var true[]

View File

@ -88,11 +88,10 @@ class NetworkBinaryStream extends BinaryStream{
$animationCount = $this->getLInt();
$animations = [];
for($i = 0; $i < $animationCount; ++$i){
$animations[] = new SkinAnimation(
$skinImage = $this->getSkinImage(),
$animationType = $this->getLInt(),
$animationFrames = $this->getLFloat()
);
$skinImage = $this->getSkinImage();
$animationType = $this->getLInt();
$animationFrames = $this->getLFloat();
$animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames);
}
$capeData = $this->getSkinImage();
$geometryData = $this->getString();
@ -107,13 +106,12 @@ class NetworkBinaryStream extends BinaryStream{
$personaPieceCount = $this->getLInt();
$personaPieces = [];
for($i = 0; $i < $personaPieceCount; ++$i){
$personaPieces[] = new PersonaSkinPiece(
$pieceId = $this->getString(),
$pieceType = $this->getString(),
$packId = $this->getString(),
$isDefaultPiece = $this->getBool(),
$productId = $this->getString()
);
$pieceId = $this->getString();
$pieceType = $this->getString();
$packId = $this->getString();
$isDefaultPiece = $this->getBool();
$productId = $this->getString();
$personaPieces[] = new PersonaSkinPiece($pieceId, $pieceType, $packId, $isDefaultPiece, $productId);
}
$pieceTintColorCount = $this->getLInt();
$pieceTintColors = [];
@ -202,9 +200,9 @@ class NetworkBinaryStream extends BinaryStream{
/** @var CompoundTag|null $nbt */
$nbt = null;
if($nbtLen === 0xffff){
$c = $this->getByte();
if($c !== 1){
throw new \UnexpectedValueException("Unexpected NBT count $c");
$nbtDataVersion = $this->getByte();
if($nbtDataVersion !== 1){
throw new \UnexpectedValueException("Unexpected NBT data version $nbtDataVersion");
}
$decodedNBT = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
if(!($decodedNBT instanceof CompoundTag)){
@ -277,7 +275,7 @@ class NetworkBinaryStream extends BinaryStream{
if($nbt !== null){
$this->putLShort(0xffff);
$this->putByte(1); //TODO: some kind of count field? always 1 as of 1.9.0
$this->putByte(1); //TODO: NBT data version (?)
$this->put((new NetworkLittleEndianNBTStream())->write($nbt));
}else{
$this->putLShort(0);
@ -473,7 +471,7 @@ class NetworkBinaryStream extends BinaryStream{
/**
* Reads and returns an EntityUniqueID
*/
public function getEntityUniqueId() : int{
final public function getEntityUniqueId() : int{
return $this->getVarLong();
}
@ -487,7 +485,7 @@ class NetworkBinaryStream extends BinaryStream{
/**
* Reads and returns an EntityRuntimeID
*/
public function getEntityRuntimeId() : int{
final public function getEntityRuntimeId() : int{
return $this->getUnsignedVarLong();
}
@ -546,11 +544,10 @@ class NetworkBinaryStream extends BinaryStream{
* Reads a floating-point Vector3 object with coordinates rounded to 4 decimal places.
*/
public function getVector3() : Vector3{
return new Vector3(
$this->getLFloat(),
$this->getLFloat(),
$this->getLFloat()
);
$x = $this->getLFloat();
$y = $this->getLFloat();
$z = $this->getLFloat();
return new Vector3($x, $y, $z);
}
/**
@ -647,14 +644,12 @@ class NetworkBinaryStream extends BinaryStream{
}
protected function getEntityLink() : EntityLink{
$link = new EntityLink();
$link->fromEntityUniqueId = $this->getEntityUniqueId();
$link->toEntityUniqueId = $this->getEntityUniqueId();
$link->type = $this->getByte();
$link->immediate = $this->getBool();
return $link;
$fromEntityUniqueId = $this->getEntityUniqueId();
$toEntityUniqueId = $this->getEntityUniqueId();
$type = $this->getByte();
$immediate = $this->getBool();
$causedByRider = $this->getBool();
return new EntityLink($fromEntityUniqueId, $toEntityUniqueId, $type, $immediate, $causedByRider);
}
protected function putEntityLink(EntityLink $link) : void{
@ -662,6 +657,7 @@ class NetworkBinaryStream extends BinaryStream{
$this->putEntityUniqueId($link->toEntityUniqueId);
$this->putByte($link->type);
$this->putBool($link->immediate);
$this->putBool($link->causedByRider);
}
protected function getCommandOriginData() : CommandOriginData{
@ -753,4 +749,12 @@ class NetworkBinaryStream extends BinaryStream{
$this->putStructureSettings($structureEditorData->structureSettings);
$this->putVarInt($structureEditorData->structureRedstoneSaveMove);
}
public function readGenericTypeNetworkId() : int{
return $this->getVarInt();
}
public function writeGenericTypeNetworkId(int $id) : void{
$this->putVarInt($id);
}
}

View File

@ -52,6 +52,7 @@ use pocketmine\network\mcpe\protocol\ClientCacheBlobStatusPacket;
use pocketmine\network\mcpe\protocol\ClientCacheMissResponsePacket;
use pocketmine\network\mcpe\protocol\ClientCacheStatusPacket;
use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket;
use pocketmine\network\mcpe\protocol\CodeBuilderPacket;
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandOutputPacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
@ -61,9 +62,12 @@ use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DebugInfoPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\EducationSettingsPacket;
use pocketmine\network\mcpe\protocol\EmoteListPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\EventPacket;
use pocketmine\network\mcpe\protocol\GameRulesChangedPacket;
@ -74,6 +78,8 @@ use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LecternUpdatePacket;
use pocketmine\network\mcpe\protocol\LevelChunkPacket;
@ -99,15 +105,20 @@ use pocketmine\network\mcpe\protocol\NetworkSettingsPacket;
use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\NpcRequestPacket;
use pocketmine\network\mcpe\protocol\OnScreenTextureAnimationPacket;
use pocketmine\network\mcpe\protocol\PacketViolationWarningPacket;
use pocketmine\network\mcpe\protocol\PhotoTransferPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerArmorDamagePacket;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\PlaySoundPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\PositionTrackingDBClientRequestPacket;
use pocketmine\network\mcpe\protocol\PositionTrackingDBServerBroadcastPacket;
use pocketmine\network\mcpe\protocol\PurchaseReceiptPacket;
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
use pocketmine\network\mcpe\protocol\RemoveEntityPacket;
@ -163,9 +174,9 @@ use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPropertiesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdatePlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\UpdateSoftEnumPacket;
use pocketmine\network\mcpe\protocol\UpdateTradePacket;
use pocketmine\network\mcpe\protocol\VideoStreamConnectPacket;
abstract class NetworkSession{
@ -666,10 +677,6 @@ abstract class NetworkSession{
return false;
}
public function handleVideoStreamConnect(VideoStreamConnectPacket $packet) : bool{
return false;
}
public function handleAddEntity(AddEntityPacket $packet) : bool{
return false;
}
@ -741,4 +748,52 @@ abstract class NetworkSession{
public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
return false;
}
public function handleCreativeContent(CreativeContentPacket $packet) : bool{
return false;
}
public function handlePlayerEnchantOptions(PlayerEnchantOptionsPacket $packet) : bool{
return false;
}
public function handleItemStackRequest(ItemStackRequestPacket $packet) : bool{
return false;
}
public function handleItemStackResponse(ItemStackResponsePacket $packet) : bool{
return false;
}
public function handlePlayerArmorDamage(PlayerArmorDamagePacket $packet) : bool{
return false;
}
public function handleCodeBuilder(CodeBuilderPacket $packet) : bool{
return false;
}
public function handleUpdatePlayerGameType(UpdatePlayerGameTypePacket $packet) : bool{
return false;
}
public function handleEmoteList(EmoteListPacket $packet) : bool{
return false;
}
public function handlePositionTrackingDBServerBroadcast(PositionTrackingDBServerBroadcastPacket $packet) : bool{
return false;
}
public function handlePositionTrackingDBClientRequest(PositionTrackingDBClientRequestPacket $packet) : bool{
return false;
}
public function handleDebugInfo(DebugInfoPacket $packet) : bool{
return false;
}
public function handlePacketViolationWarning(PacketViolationWarningPacket $packet) : bool{
return false;
}
}

View File

@ -53,7 +53,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
* Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't
* communicate. It's important that we check this to avoid catastrophes.
*/
private const MCPE_RAKNET_PROTOCOL_VERSION = 9;
private const MCPE_RAKNET_PROTOCOL_VERSION = 10;
/** @var Server */
private $server;

View File

@ -35,10 +35,12 @@ use function json_decode;
use function ltrim;
use function openssl_verify;
use function ord;
use function serialize;
use function str_split;
use function strlen;
use function strtr;
use function time;
use function unserialize;
use function wordwrap;
use const OPENSSL_ALGO_SHA384;
@ -48,8 +50,10 @@ class VerifyLoginTask extends AsyncTask{
private const CLOCK_DRIFT_MAX = 60;
/** @var LoginPacket */
private $packet;
/** @var string */
private $chainJwts;
/** @var string */
private $clientDataJwt;
/**
* @var string|null
@ -66,23 +70,25 @@ class VerifyLoginTask extends AsyncTask{
private $authenticated = false;
public function __construct(Player $player, LoginPacket $packet){
$this->storeLocal($player);
$this->packet = $packet;
$this->storeLocal([$player, $packet]);
$this->chainJwts = serialize($packet->chainData["chain"]);
$this->clientDataJwt = $packet->clientDataJwt;
}
public function onRun(){
$packet = $this->packet; //Get it in a local variable to make sure it stays unserialized
/** @var string[] $chainJwts */
$chainJwts = unserialize($this->chainJwts); //Get it in a local variable to make sure it stays unserialized
try{
$currentKey = null;
$first = true;
foreach($packet->chainData["chain"] as $jwt){
foreach($chainJwts as $jwt){
$this->validateToken($jwt, $currentKey, $first);
$first = false;
}
$this->validateToken($packet->clientDataJwt, $currentKey);
$this->validateToken($this->clientDataJwt, $currentKey);
$this->error = null;
}catch(VerifyLoginException $e){
@ -109,6 +115,9 @@ class VerifyLoginTask extends AsyncTask{
//First link, check that it is self-signed
$currentPublicKey = $headers["x5u"];
}elseif($headers["x5u"] !== $currentPublicKey){
//Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway
throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.badSignature");
}
$plainSignature = base64_decode(strtr($sigB64, '-_', '+/'), true);
@ -160,12 +169,15 @@ class VerifyLoginTask extends AsyncTask{
}
public function onCompletion(Server $server){
/** @var Player $player */
$player = $this->fetchLocal();
/**
* @var Player $player
* @var LoginPacket $packet
*/
[$player, $packet] = $this->fetchLocal();
if(!$player->isConnected()){
$server->getLogger()->error("Player " . $player->getName() . " was disconnected before their login could be verified");
}else{
$player->onVerifyCompleted($this->packet, $this->error, $this->authenticated);
$player->onVerifyCompleted($packet, $this->error, $this->authenticated);
}
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\nbt\tag\CompoundTag;
final class R12ToCurrentBlockMapEntry{
/** @var string */
private $id;
/** @var int */
private $meta;
/** @var CompoundTag */
private $blockState;
public function __construct(string $id, int $meta, CompoundTag $blockState){
$this->id = $id;
$this->meta = $meta;
$this->blockState = $blockState;
}
public function getId() : string{
return $this->id;
}
public function getMeta() : int{
return $this->meta;
}
public function getBlockState() : CompoundTag{
return $this->blockState;
}
public function __toString(){
return "id=$this->id, meta=$this->meta, nbt=$this->blockState";
}
}

View File

@ -21,13 +21,14 @@
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol\types;
namespace pocketmine\network\mcpe\convert;
use pocketmine\block\BlockIds;
use pocketmine\nbt\NBT;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\network\mcpe\NetworkBinaryStream;
use function file_get_contents;
use function getmypid;
use function json_decode;
@ -66,9 +67,22 @@ final class RuntimeBlockMapping{
private static function setupLegacyMappings() : void{
$legacyIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/block_id_map.json"), true);
$legacyStateMap = (new NetworkLittleEndianNBTStream())->read(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/r12_to_current_block_map.nbt"));
if(!($legacyStateMap instanceof ListTag) or $legacyStateMap->getTagType() !== NBT::TAG_Compound){
throw new \RuntimeException("Invalid legacy states mapping table, expected TAG_List<TAG_Compound> root");
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = [];
$legacyStateMapReader = new NetworkBinaryStream(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/r12_to_current_block_map.bin"));
$nbtReader = new NetworkLittleEndianNBTStream();
while(!$legacyStateMapReader->feof()){
$id = $legacyStateMapReader->getString();
$meta = $legacyStateMapReader->getLShort();
$offset = $legacyStateMapReader->getOffset();
$state = $nbtReader->read($legacyStateMapReader->getBuffer(), false, $offset);
$legacyStateMapReader->setOffset($offset);
if(!($state instanceof CompoundTag)){
throw new \RuntimeException("Blockstate should be a TAG_Compound");
}
$legacyStateMap[] = new R12ToCurrentBlockMapEntry($id, $meta, $state);
}
/**
@ -78,16 +92,17 @@ final class RuntimeBlockMapping{
foreach(self::$bedrockKnownStates as $k => $state){
$idToStatesMap[$state->getCompoundTag("block")->getString("name")][] = $k;
}
/** @var CompoundTag $pair */
foreach($legacyStateMap as $pair){
$oldState = $pair->getCompoundTag("old");
$id = $legacyIdMap[$oldState->getString("name")];
$data = $oldState->getShort("val");
$id = $legacyIdMap[$pair->getId()] ?? null;
if($id === null){
throw new \RuntimeException("No legacy ID matches " . $pair->getId());
}
$data = $pair->getMeta();
if($data > 15){
//we can't handle metadata with more than 4 bits
continue;
}
$mappedState = $pair->getCompoundTag("new");
$mappedState = $pair->getBlockState();
//TODO HACK: idiotic NBT compare behaviour on 3.x compares keys which are stored by values
$mappedState->setName("block");

View File

@ -87,6 +87,7 @@ class ActorEventPacket extends DataPacket{
public const TREASURE_HUNT = 72;
public const AGENT_SUMMON = 73;
public const CHARGED_CROSSBOW = 74;
public const FALL = 75;
//TODO: add more events

View File

@ -32,6 +32,7 @@ use function get_class;
use function strlen;
use function zlib_decode;
use function zlib_encode;
use const ZLIB_ENCODING_RAW;
#ifndef COMPILE
use pocketmine\utils\Binary;
#endif
@ -71,7 +72,7 @@ class BatchPacket extends DataPacket{
}
protected function encodePayload(){
$this->put(zlib_encode($this->payload, ZLIB_ENCODING_DEFLATE, $this->compressionLevel));
$this->put(zlib_encode($this->payload, ZLIB_ENCODING_RAW, $this->compressionLevel));
}
/**

View File

@ -0,0 +1,66 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class CodeBuilderPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::CODE_BUILDER_PACKET;
/** @var string */
private $url;
/** @var bool */
private $openCodeBuilder;
public static function create(string $url, bool $openCodeBuilder) : self{
$result = new self;
$result->url = $url;
$result->openCodeBuilder = $openCodeBuilder;
return $result;
}
public function getUrl() : string{
return $this->url;
}
public function openCodeBuilder() : bool{
return $this->openCodeBuilder;
}
protected function decodePayload() : void{
$this->url = $this->getString();
$this->openCodeBuilder = $this->getBool();
}
protected function encodePayload() : void{
$this->putString($this->url);
$this->putBool($this->openCodeBuilder);
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleCodeBuilder($this);
}
}

View File

@ -97,6 +97,7 @@ class CraftingDataPacket extends DataPacket{
$entry["uuid"] = $this->getUUID()->toString();
$entry["block"] = $this->getString();
$entry["priority"] = $this->getVarInt();
$entry["net_id"] = $this->readGenericTypeNetworkId();
break;
case self::ENTRY_SHAPED:
@ -118,6 +119,7 @@ class CraftingDataPacket extends DataPacket{
$entry["uuid"] = $this->getUUID()->toString();
$entry["block"] = $this->getString();
$entry["priority"] = $this->getVarInt();
$entry["net_id"] = $this->readGenericTypeNetworkId();
break;
case self::ENTRY_FURNACE:
@ -140,6 +142,7 @@ class CraftingDataPacket extends DataPacket{
break;
case self::ENTRY_MULTI:
$entry["uuid"] = $this->getUUID()->toString();
$entry["net_id"] = $this->readGenericTypeNetworkId();
break;
default:
throw new \UnexpectedValueException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode
@ -148,9 +151,12 @@ class CraftingDataPacket extends DataPacket{
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$input = $this->getVarInt();
$inputMeta = $this->getVarInt();
$ingredient = $this->getVarInt();
$ingredientMeta = $this->getVarInt();
$output = $this->getVarInt();
$this->potionTypeRecipes[] = new PotionTypeRecipe($input, $ingredient, $output);
$outputMeta = $this->getVarInt();
$this->potionTypeRecipes[] = new PotionTypeRecipe($input, $inputMeta, $ingredient, $ingredientMeta, $output, $outputMeta);
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$input = $this->getVarInt();
@ -193,6 +199,7 @@ class CraftingDataPacket extends DataPacket{
$stream->put(str_repeat("\x00", 16)); //Null UUID
$stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks)
$stream->putVarInt(50); //TODO: priority
$stream->writeGenericTypeNetworkId($pos); //TODO: ANOTHER recipe ID, only used on the network
return CraftingDataPacket::ENTRY_SHAPELESS;
}
@ -217,6 +224,7 @@ class CraftingDataPacket extends DataPacket{
$stream->put(str_repeat("\x00", 16)); //Null UUID
$stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks)
$stream->putVarInt(50); //TODO: priority
$stream->writeGenericTypeNetworkId($pos); //TODO: ANOTHER recipe ID, only used on the network
return CraftingDataPacket::ENTRY_SHAPED;
}
@ -272,9 +280,12 @@ class CraftingDataPacket extends DataPacket{
}
$this->putUnsignedVarInt(count($this->potionTypeRecipes));
foreach($this->potionTypeRecipes as $recipe){
$this->putVarInt($recipe->getInputPotionType());
$this->putVarInt($recipe->getInputItemId());
$this->putVarInt($recipe->getInputItemMeta());
$this->putVarInt($recipe->getIngredientItemId());
$this->putVarInt($recipe->getOutputPotionType());
$this->putVarInt($recipe->getIngredientItemMeta());
$this->putVarInt($recipe->getOutputItemId());
$this->putVarInt($recipe->getOutputItemMeta());
}
$this->putUnsignedVarInt(count($this->potionContainerRecipes));
foreach($this->potionContainerRecipes as $recipe){

View File

@ -0,0 +1,67 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use function count;
class CreativeContentPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::CREATIVE_CONTENT_PACKET;
/** @var CreativeContentEntry[] */
private $entries;
/**
* @param CreativeContentEntry[] $entries
*/
public static function create(array $entries) : self{
$result = new self;
$result->entries = $entries;
return $result;
}
/** @return CreativeContentEntry[] */
public function getEntries() : array{ return $this->entries; }
protected function decodePayload() : void{
$this->entries = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->entries[] = CreativeContentEntry::read($this);
}
}
protected function encodePayload() : void{
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$entry->write($this);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleCreativeContent($this);
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class DebugInfoPacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::DEBUG_INFO_PACKET;
/** @var int */
private $entityUniqueId;
/** @var string */
private $data;
public static function create(int $entityUniqueId, string $data) : self{
$result = new self;
$result->entityUniqueId = $entityUniqueId;
$result->data = $data;
return $result;
}
/**
* TODO: we can't call this getEntityRuntimeId() because of base class collision (crap architecture, thanks Shoghi)
*/
public function getEntityUniqueIdField() : int{ return $this->entityUniqueId; }
public function getData() : string{ return $this->data; }
protected function decodePayload() : void{
$this->entityUniqueId = $this->getEntityUniqueId();
$this->data = $this->getString();
}
protected function encodePayload() : void{
$this->putEntityUniqueId($this->entityUniqueId);
$this->putString($this->data);
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleDebugInfo($this);
}
}

View File

@ -32,12 +32,21 @@ class EducationSettingsPacket extends DataPacket{
/** @var string */
private $codeBuilderDefaultUri;
/** @var string */
private $codeBuilderTitle;
/** @var bool */
private $canResizeCodeBuilder;
/** @var string|null */
private $codeBuilderOverrideUri;
/** @var bool */
private $hasQuiz;
public static function create(string $codeBuilderDefaultUri, bool $hasQuiz) : self{
public static function create(string $codeBuilderDefaultUri, string $codeBuilderTitle, bool $canResizeCodeBuilder, ?string $codeBuilderOverrideUri, bool $hasQuiz) : self{
$result = new self;
$result->codeBuilderDefaultUri = $codeBuilderDefaultUri;
$result->codeBuilderTitle = $codeBuilderTitle;
$result->canResizeCodeBuilder = $canResizeCodeBuilder;
$result->codeBuilderOverrideUri = $codeBuilderOverrideUri;
$result->hasQuiz = $hasQuiz;
return $result;
}
@ -46,17 +55,42 @@ class EducationSettingsPacket extends DataPacket{
return $this->codeBuilderDefaultUri;
}
public function getCodeBuilderTitle() : string{
return $this->codeBuilderTitle;
}
public function canResizeCodeBuilder() : bool{
return $this->canResizeCodeBuilder;
}
public function getCodeBuilderOverrideUri() : ?string{
return $this->codeBuilderOverrideUri;
}
public function getHasQuiz() : bool{
return $this->hasQuiz;
}
protected function decodePayload() : void{
$this->codeBuilderDefaultUri = $this->getString();
$this->codeBuilderTitle = $this->getString();
$this->canResizeCodeBuilder = $this->getBool();
if($this->getBool()){
$this->codeBuilderOverrideUri = $this->getString();
}else{
$this->codeBuilderOverrideUri = null;
}
$this->hasQuiz = $this->getBool();
}
protected function encodePayload() : void{
$this->putString($this->codeBuilderDefaultUri);
$this->putString($this->codeBuilderTitle);
$this->putBool($this->canResizeCodeBuilder);
$this->putBool($this->codeBuilderOverrideUri !== null);
if($this->codeBuilderOverrideUri !== null){
$this->putString($this->codeBuilderOverrideUri);
}
$this->putBool($this->hasQuiz);
}

View File

@ -0,0 +1,74 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\utils\UUID;
use function count;
class EmoteListPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::EMOTE_LIST_PACKET;
/** @var int */
private $playerEntityRuntimeId;
/** @var UUID[] */
private $emoteIds;
/**
* @param UUID[] $emoteIds
*/
public static function create(int $playerEntityRuntimeId, array $emoteIds) : self{
$result = new self;
$result->playerEntityRuntimeId = $playerEntityRuntimeId;
$result->emoteIds = $emoteIds;
return $result;
}
public function getPlayerEntityRuntimeId() : int{ return $this->playerEntityRuntimeId; }
/** @return UUID[] */
public function getEmoteIds() : array{ return $this->emoteIds; }
protected function decodePayload() : void{
$this->playerEntityRuntimeId = $this->getEntityRuntimeId();
$this->emoteIds = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->emoteIds[] = $this->getUUID();
}
}
protected function encodePayload() : void{
$this->putEntityRuntimeId($this->playerEntityRuntimeId);
$this->putUnsignedVarInt(count($this->emoteIds));
foreach($this->emoteIds as $emoteId){
$this->putUUID($emoteId);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleEmoteList($this);
}
}

View File

@ -30,7 +30,7 @@ use pocketmine\network\mcpe\NetworkSession;
class EmotePacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::EMOTE_PACKET;
private const FLAG_SERVER = 1 << 0;
public const FLAG_SERVER = 1 << 0;
/** @var int */
private $entityRuntimeId;

View File

@ -30,14 +30,18 @@ use pocketmine\network\mcpe\NetworkSession;
class HurtArmorPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::HURT_ARMOR_PACKET;
/** @var int */
public $cause;
/** @var int */
public $health;
protected function decodePayload(){
$this->cause = $this->getVarInt();
$this->health = $this->getVarInt();
}
protected function encodePayload(){
$this->putVarInt($this->cause);
$this->putVarInt($this->health);
}

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use function count;
class InventoryContentPacket extends DataPacket{
@ -34,14 +34,14 @@ class InventoryContentPacket extends DataPacket{
/** @var int */
public $windowId;
/** @var Item[] */
/** @var ItemStackWrapper[] */
public $items = [];
protected function decodePayload(){
$this->windowId = $this->getUnsignedVarInt();
$count = $this->getUnsignedVarInt();
for($i = 0; $i < $count; ++$i){
$this->items[] = $this->getSlot();
$this->items[] = ItemStackWrapper::read($this);
}
}
@ -49,7 +49,7 @@ class InventoryContentPacket extends DataPacket{
$this->putUnsignedVarInt($this->windowId);
$this->putUnsignedVarInt(count($this->items));
foreach($this->items as $item){
$this->putSlot($item);
$item->write($this);
}
}

View File

@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\item\Item;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
class InventorySlotPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::INVENTORY_SLOT_PACKET;
@ -35,19 +35,19 @@ class InventorySlotPacket extends DataPacket{
public $windowId;
/** @var int */
public $inventorySlot;
/** @var Item */
/** @var ItemStackWrapper */
public $item;
protected function decodePayload(){
$this->windowId = $this->getUnsignedVarInt();
$this->inventorySlot = $this->getUnsignedVarInt();
$this->item = $this->getSlot();
$this->item = ItemStackWrapper::read($this);
}
protected function encodePayload(){
$this->putUnsignedVarInt($this->windowId);
$this->putUnsignedVarInt($this->inventorySlot);
$this->putSlot($this->item);
$this->item->write($this);
}
public function handle(NetworkSession $session) : bool{

View File

@ -26,7 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\InventoryTransactionChangedSlotsHack;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
use function count;
@ -50,20 +50,14 @@ class InventoryTransactionPacket extends DataPacket{
public const USE_ITEM_ON_ENTITY_ACTION_ATTACK = 1;
/** @var int */
public $transactionType;
public $requestId;
/** @var InventoryTransactionChangedSlotsHack[] */
public $requestChangedSlots;
/**
* @var bool
* NOTE: THIS FIELD DOES NOT EXIST IN THE PROTOCOL, it's merely used for convenience for PocketMine-MP to easily
* determine whether we're doing a crafting transaction.
*/
public $isCraftingPart = false;
/**
* @var bool
* NOTE: THIS FIELD DOES NOT EXIST IN THE PROTOCOL, it's merely used for convenience for PocketMine-MP to easily
* determine whether we're doing a crafting transaction.
*/
public $isFinalCraftingPart = false;
/** @var int */
public $transactionType;
/** @var bool */
public $hasItemStackIds;
/** @var NetworkInventoryAction[] */
public $actions = [];
@ -72,29 +66,20 @@ class InventoryTransactionPacket extends DataPacket{
public $trData;
protected function decodePayload(){
$this->requestId = $this->readGenericTypeNetworkId();
$this->requestChangedSlots = [];
if($this->requestId !== 0){
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->requestChangedSlots[] = InventoryTransactionChangedSlotsHack::read($this);
}
}
$this->transactionType = $this->getUnsignedVarInt();
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->actions[] = $action = (new NetworkInventoryAction())->read($this);
$this->hasItemStackIds = $this->getBool();
if(
$action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER and
$action->windowId === ContainerIds::UI and
$action->inventorySlot === 50 and
!$action->oldItem->equalsExact($action->newItem)
){
$this->isCraftingPart = true;
if(!$action->oldItem->isNull() and $action->newItem->isNull()){
$this->isFinalCraftingPart = true;
}
}elseif(
$action->sourceType === NetworkInventoryAction::SOURCE_TODO and (
$action->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
$action->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
)
){
$this->isCraftingPart = true;
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->actions[] = $action = (new NetworkInventoryAction())->read($this, $this->hasItemStackIds);
}
$this->trData = new \stdClass();
@ -134,11 +119,21 @@ class InventoryTransactionPacket extends DataPacket{
}
protected function encodePayload(){
$this->writeGenericTypeNetworkId($this->requestId);
if($this->requestId !== 0){
$this->putUnsignedVarInt(count($this->requestChangedSlots));
foreach($this->requestChangedSlots as $changedSlots){
$changedSlots->write($this);
}
}
$this->putUnsignedVarInt($this->transactionType);
$this->putBool($this->hasItemStackIds);
$this->putUnsignedVarInt(count($this->actions));
foreach($this->actions as $action){
$action->write($this);
$action->write($this, $this->hasItemStackIds);
}
switch($this->transactionType){

View File

@ -0,0 +1,67 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest;
use function count;
class ItemStackRequestPacket extends DataPacket/* implements ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::ITEM_STACK_REQUEST_PACKET;
/** @var ItemStackRequest[] */
private $requests;
/**
* @param ItemStackRequest[] $requests
*/
public static function create(array $requests) : self{
$result = new self;
$result->requests = $requests;
return $result;
}
/** @return ItemStackRequest[] */
public function getRequests() : array{ return $this->requests; }
protected function decodePayload() : void{
$this->requests = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->requests[] = ItemStackRequest::read($this);
}
}
protected function encodePayload() : void{
$this->putUnsignedVarInt(count($this->requests));
foreach($this->requests as $request){
$request->write($this);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleItemStackRequest($this);
}
}

View File

@ -0,0 +1,67 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
use function count;
class ItemStackResponsePacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::ITEM_STACK_RESPONSE_PACKET;
/** @var ItemStackResponse[] */
private $responses;
/**
* @param ItemStackResponse[] $responses
*/
public static function create(array $responses) : self{
$result = new self;
$result->responses = $responses;
return $result;
}
/** @return ItemStackResponse[] */
public function getResponses() : array{ return $this->responses; }
protected function decodePayload() : void{
$this->responses = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->responses[] = ItemStackResponse::read($this);
}
}
protected function encodePayload() : void{
$this->putUnsignedVarInt(count($this->responses));
foreach($this->responses as $response){
$response->write($this);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleItemStackResponse($this);
}
}

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