Compare commits

..

133 Commits

Author SHA1 Message Date
7effa03ba4 Release 4.0.0-BETA7 2021-10-28 17:28:48 +01:00
d0474ccd92 make-release: note which channel the build will be released into 2021-10-28 16:29:20 +01:00
2b0768f720 make-release: fixed retention of +dev on release versions 2021-10-28 16:26:56 +01:00
dba148cfaa build/make-release: make arg parsing use getopt 2021-10-28 16:25:18 +01:00
48f77abe7e Leave channel ID in VersionInfo
so that I don't have to type it out every time I make a new release. Most of the time it's going to be posted to the same channel as before anyway.
2021-10-28 16:03:43 +01:00
bb05af103d PluginManager: fixed crash when using a plugin-loader plugin (read: devtools)
closes #4518
2021-10-28 15:55:05 +01:00
0ef5c67b9b Use static constructor for MovePlayerPacket
this marks the last of the packets created using the old way.
2021-10-27 21:10:16 +01:00
6d89265510 Player: reduce code duplication
back when this was just hardcoded >> 4 everywhere, nobody thought anything of it, but now it uses constants, it's easy to cross-reference and see where the duplicates are.
2021-10-26 23:02:50 +01:00
a7d8a598e1 World: reduce code duplication for chunk coordinate calculation 2021-10-26 22:58:17 +01:00
51fbff204b World: make PhpStorm understand return type of getAdjacentChunks() 2021-10-26 20:32:09 +01:00
1873457840 PopulationTask: stop using dynamic properties 2021-10-26 20:21:58 +01:00
fca70efbb1 World: move chunk population related methods to be in the same overall place 2021-10-26 16:44:08 +01:00
8f88393184 World: Specialize generateChunkCallback() for PopulationTask
this allows us to also set the adjacent chunks before calling ChunkPopulateEvent, to give a more accurate picture of what changed.
2021-10-26 15:28:00 +01:00
b9d9b69bbe ConsoleReaderThread: trim the string before returning it
it will have a newline at the end that was added by the subprocess when posting it to the main process.
2021-10-26 01:07:14 +01:00
1d99cd329a CS again 2021-10-26 00:50:43 +01:00
bd8cba1a7f Added unit tests for Utils::testValidInstance() 2021-10-26 00:49:41 +01:00
24d4daec90 Utils::testValidInstance() now accepts interfaces for the baseName 2021-10-26 00:32:32 +01:00
4178c81209 Utils: fixed testValidInstance() not accepting the same valid class for both className and baseName
this caused problems in PlayerCreationEvent because plugins set the base class and then set the player class to the same thing.
2021-10-26 00:31:30 +01:00
94f4ef5862 PopulationTask: Throw AssumptionFailedError if center chunk is null for some reason 2021-10-25 21:07:03 +01:00
2e2515354c PopulationTask: fixed undefined method call
fuck you PhpStorm! fuck you PhpStorm! fuck you PhpStorm!
2021-10-25 20:57:43 +01:00
359d0835f3 CS 2021-10-25 20:54:39 +01:00
d4cbde6f10 PopulationTask: use modification counters to detect changed chunks
instead of using terrain dirty flags, which aren't suitable for this purpose
2021-10-25 20:53:50 +01:00
a5418a019d Chunk: added modification counter
this is independent from the terrain dirty flags (which are specifically used to track state of chunks needing to be saved).
2021-10-25 20:53:11 +01:00
baba25953f Chunk: make all parameters of __construct() mandatory and non-nullable
having the constructor fill in defaults for these invariably causes bugs.
2021-10-25 20:22:50 +01:00
d53347454b Chunk: use HeightArray::fill() 2021-10-25 20:17:30 +01:00
401e8d117b Flat: use a less dumb way to build biome array 2021-10-25 20:15:33 +01:00
9835d75f65 Chunk: removed heighArray parameter from constructor
we don't pass this anywhere, and really it should be dynamically initialized anyway, just like light.
2021-10-25 20:13:50 +01:00
b8519d1af4 World: fixed every chunk having terrain saved at least once, even if unmodified
setPopulated() sets dirty flags on the chunk, causing the autosave sweep
to think they've been changed when they haven't. We now pass
terrainPopulated to the constructor to avoid this ambiguity recurring in
the future.
2021-10-25 19:53:47 +01:00
42ede30e77 ... 2021-10-23 23:57:28 +01:00
04aedc6494 Updated BedrockProtocol 2021-10-23 23:54:49 +01:00
701a71a4ee Sound::encode() position is no longer nullable
making this nullable was based on the invalid assumption that global sounds have no position, but it turns out they _do_ still use the position to make the sound come from the correct direction.
2021-10-23 02:01:26 +01:00
e50072dc27 Clean PHPStan baselines 2021-10-23 01:55:10 +01:00
c77829f4ad Migrate packet creation to use ::create() methods in all but one case
MovePlayerPacket doesn't yet have a ::create() due to a complication with fields that aren't always present.
2021-10-23 01:46:01 +01:00
c773e43eda Updated BedrockProtocol to pmmp/BedrockProtocol@97fa88e9ef 2021-10-23 01:16:45 +01:00
c262c2e726 Updated composer dependencies 2021-10-23 01:14:54 +01:00
a4b65d6a3f PlayerCreationEvent: verify that the class actually exists and is instantiable
this ensures that crashdumps blame the plugin instead of the core on bad classes, such as in this case: https://crash.pmmp.io/view/5331225
2021-10-21 20:37:45 +01:00
2971bf30a5 actions: combine code verify into one step
this way the diff takes one less click to get to.
2021-10-21 00:30:19 +01:00
4f2bcb61d6 Fixed crashdump generation when crashing before PluginManager was created 2021-10-20 23:35:04 +01:00
e2275cc8ec PluginManager: Prevent infinite recursion in loadPlugins()
if a plugin calls loadPlugins(server->getPluginPath()) during its onLoad(), and it itself is in that plugin path, an infinite recursion will occur.
2021-10-20 23:10:18 +01:00
620874d902 PluginManager: Extract checkPluginLoadability() to a PluginLoadabilityChecker unit
this can be more easily unit-tested.
2021-10-20 22:31:56 +01:00
44508a138f Moved plugin extension requirement checks to PluginManager::checkPluginLoadability()
these don't really belong in PluginDescription.
2021-10-20 22:13:30 +01:00
aa408c9a97 Fixed 9646128d01 2021-10-20 21:54:57 +01:00
6d78a0b435 CS 2021-10-20 21:52:42 +01:00
76b4b23d98 PluginManager: remove loadPlugin()
loadPlugins() is now the preferred option, since it does all the proper checks.
In addition, the server now acknowledges that loading a single plugin may cause multiple plugins to be loaded, so returning only a single Plugin is not representative of what's actually happening.
2021-10-20 21:52:19 +01:00
03fcd844eb PluginManager::loadPlugins() now accepts files as well as directories
loadPlugins() is now a superior option to loadPlugin(), since it enforces dependency checks and also supports automatic loading of plugins when new loaders are installed.
2021-10-20 21:36:14 +01:00
fecc13f362 Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-10-20 21:23:14 +01:00
9646128d01 Updated resources/locale submodule to pmmp/Language@09c709f242 2021-10-20 21:22:56 +01:00
a788954551 Fixed dependency handling across plugin loaders (#3971) 2021-10-20 20:22:00 +01:00
ec3986827c Update BedrockProtocol to 3.0.1, widen constraint to allow newer patch versions 2021-10-20 16:20:10 +01:00
09c840b66a Update transient composer dependencies 2021-10-20 16:19:20 +01:00
80b402e529 ItemTranslator: throw the proper exceptions when failing to map network IDs 2021-10-20 14:01:39 +01:00
a3f8546ac4 4.0.0-BETA7 is next 2021-10-19 19:13:43 +01:00
46920818b5 Release 4.0.0-BETA6 2021-10-19 19:13:43 +01:00
69cb575789 Merge branch 'stable' 2021-10-19 19:05:25 +01:00
fee6478cbe Updated BedrockData and BedrockProtocol for 1.17.40 support 2021-10-19 19:00:29 +01:00
9c5cec77b1 3.25.1 is next 2021-10-19 18:27:30 +01:00
f48b703533 Release 3.25.0 2021-10-19 18:27:26 +01:00
70636f6eb4 Protocol changes for 1.17.40 2021-10-19 18:00:34 +01:00
c70b80c273 ItemEntity: implement partial itemstack pickups in the dumbest way possible
Given the various limitations and flexibilities posed by EntityItemPickupEvent, I settled on this as the simplest way to deal with the problem.

- EntityItemPickupEvent may have its destination inventory changed, so we can't cache the result of getAddableItemQuantity() to use after the event.
- The item itself may have changed, so even if we thought we could add some items before the change, we might not be able to afterwards.

Considering the above facts, it's better to just give the whole itemstack to EntityItemPickupEvent, and let plugins use getAddableItemQuantity() on their own to decide if their chosen inventory can accommodate the item or not.
If it can't, then we'll just drop it on the ground.
This also fixes a potential issue where plugins changing the item to a custom one might end up with their items and the actual items both just vanishing if the target inventory was full.
closes #4499
2021-10-17 22:37:49 +01:00
a794d24c81 Added a tool to generate a Markdown document of all core permissions 2021-10-17 17:02:18 +01:00
8db5732b44 Drop respect/validation
it's not worth this turning into compatibility baggage just so that we can parse plugin_list.yml, especially when we have new ways to handle data parsing coming in the pipeline.
For something as small as plugin_list.yml, it's easier (and in this case better too) to just validate it manually (respect/validation was anyway too strict considering it's YAML we're dealing with).
2021-10-15 17:15:46 +01:00
48f809d3fa Removed another dead PHPStan error pattern
this was actually a PHPStan bug fixed in 0.12.99.
2021-10-15 17:01:09 +01:00
0348236860 fucking CS again 2021-10-14 15:56:50 +01:00
8c07748100 RakLibInterface: print packet exception info as a block using Utils::printableExceptionInfo() 2021-10-14 15:55:08 +01:00
06e7338ff9 Move exception printing utilities from MainLogger to Utils
where they can be useful to other stuff apart from just the logger
2021-10-14 15:54:20 +01:00
bdbfa70558 Server: break some isolated stuff out of Server::__construct() 2021-10-14 15:44:18 +01:00
7a4af7a0bc SignalHandler: fix CS
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2021-10-14 15:14:27 +01:00
34b1392598 Cross-platform signal handler 2021-10-14 15:03:11 +01:00
321345fcc8 Sapling: simplify condition 2021-10-13 23:00:38 +01:00
0ac9f4fe61 BlockFactory: move SweetBerryBush to its proper place 2021-10-13 22:26:51 +01:00
2db53775e0 Sort item_from_string_bc_map using SORT_NATURAL 2021-10-13 21:01:59 +01:00
8523f0fb0b CS fix 2021-10-13 20:31:24 +01:00
b570324288 LegacyStringToItemParser: rely exclusively on item_from_string_bc_map.json, do not interpret integers given as strings
fixes #4507
2021-10-13 20:29:18 +01:00
6284cd14c7 LegacyStringToItemParser: added getMappings() 2021-10-13 20:19:44 +01:00
ce8af4e3bc 4.0.0-BETA6 is next 2021-10-13 00:02:01 +01:00
b65e89b605 Release 4.0.0-BETA5 2021-10-13 00:01:56 +01:00
d3f74d6ce1 Merge branch 'stable' 2021-10-12 23:32:43 +01:00
bbd925abc4 Merge commit '974d08efd62c52c1c8ac92cb1b67ac157908fd71' 2021-10-12 23:31:50 +01:00
4bf6205a6c Merge commit '289553fa46fc26b03db73db23481a98d6ddb12a5' 2021-10-12 23:28:35 +01:00
b5699679ef Merge commit 'e38866c4ba90f8efd5630dbe674fd7ca15f586ff' 2021-10-12 23:23:27 +01:00
824a89edfe Merge commit 'e032b8fe208a053441c9fbd377209740008cddb8' 2021-10-12 23:12:17 +01:00
ead9aae23c Updated build/php submodule to pmmp/php-build-scripts@fab0cbeaae 2021-10-12 23:10:06 +01:00
aefa0afd7c DefaultPermissions: Order registrations alphabetically 2021-10-12 22:17:46 +01:00
ec2699ffee DefaultPermissions: fix description of timings command permission 2021-10-12 22:16:20 +01:00
49c1e4c06e Implement fletching table (#4501) 2021-10-12 21:21:05 +01:00
9b94a4661b ItemTranslator: Use LegacyItemIdToStringMap instead of reading files directly 2021-10-11 22:17:40 +01:00
62f11360ee Added unit tests for getAddableItemQuantity() 2021-10-11 21:52:27 +01:00
a5833327f0 Inventory: added getAddableItemQuantity()
this mostly reuses the code from canAddItem().
2021-10-11 21:46:27 +01:00
d73ea8efe4 FlatGeneratorOptions: Do not hardcode biome ID 2021-10-11 21:32:20 +01:00
835e18ce6e Changelog: Changed utils\Color to color\Color (#4502)
[ci skip]
2021-10-11 20:15:44 +01:00
01c0602043 Server: do not attempt to generate a new world if it already exists 2021-10-11 17:48:08 +01:00
8fd475f87b WorldManager: Check generator options of worlds before loading them, too 2021-10-11 17:44:38 +01:00
34f54750c8 Added support for creation-time validation of generator options, closes #2717 2021-10-11 17:37:47 +01:00
092aabeb97 fix CS 2021-10-11 17:21:19 +01:00
89d7b7198f Server: drop support for tagging generator options onto the 'generator' key in pocketmine.yml
the 'preset' key should be used for this purpose instead.

This couldn't be dropped until now due to the shitty handling of unknown generators.
2021-10-11 17:20:49 +01:00
70deea0ef9 Flat: Move preset handling to a FlatGeneratorOptions unit 2021-10-11 16:53:08 +01:00
859cdfa5d2 GeneratorManager: removed unused parameter from getGenerator() 2021-10-11 16:18:38 +01:00
fa93a8d78f Server: Error on unknown generators when generating new worlds from config, instead of silently using DEFAULT
this is consistent with the behaviour of loading worlds.
2021-10-11 16:13:32 +01:00
7b6632941d GeneratorManager::getGenerator() now returns null for unknown generator aliases
instead of returning Normal::class (indistinguishable from successful match) or throwing an exception (pain in the ass to handle).
2021-10-11 16:04:36 +01:00
e62794e4cf TypeConverter: fixed PHPStan errors 2021-10-11 15:17:32 +01:00
500c298aaf Disallow the use of @handleCancelled on non-cancellable events
closes #3464
2021-10-11 15:12:16 +01:00
8ac16345a3 TypeConverter: account for items without properly mapped IDs
fixes #4459
2021-10-11 15:05:08 +01:00
19a66a8d03 committing the new strings would have helped ... 2021-10-11 01:14:00 +01:00
6d728e8d98 PluginManager: Improved startup performance when loading many plugins
for some reason we were reading and parsing the plugin.yml at least twice for every plugin loaded.
We were repeating work already done by the initial loadPlugins() triage (discovering correct loader, loading plugin.yml from disk, parsing plugin.yml, validating plugin.yml) every time loadPlugin() was called with that plugin.
2021-10-11 01:11:59 +01:00
e1ee320c8d PluginManager: Localize plugin loading error messages 2021-10-11 00:58:33 +01:00
965a16d19d PluginManager: Extract deterministic plugin loadability checks into a separate method 2021-10-11 00:49:32 +01:00
5bae458a91 changelog: mention that Entity->setPosition(AndRotation)() are now protected 2021-10-10 23:32:40 +01:00
2696698926 ClosureTask: relax closure checks to allow arrow functions without return typehints
nobody uses return typehints on arrow functions anyway .. they just waste space.
2021-10-10 23:31:57 +01:00
912e612743 Utils: allow validateCallableSignature() to accept a manually constructed CallbackType instead of a closure
this allows more fine-grained control without PHPStan yelling at us.
2021-10-10 23:27:09 +01:00
fd2df637b6 Block: rename getPositionOffset() -> getModelPositionOffset()
this gives a better idea of what the function does, and is also much less annoying for auto complete.
2021-10-10 22:35:38 +01:00
aa53dc6709 Entity: fixed network properties not updating when fireTicks changes
another bug that LBSG knew about, but didn't report. :/
2021-10-09 23:57:37 +01:00
c1f843a42c GarbageCollectorCommand: fixed duplicate MB suffix 2021-10-09 23:57:36 +01:00
09715906c8 StructureGrowEvent: added API to get the player who caused the growth (#4445) 2021-10-09 22:51:46 +01:00
13068ba3a7 3.24.1 is next 2021-10-09 20:20:41 +01:00
b54854529f Release 3.24.0 2021-10-09 20:20:37 +01:00
974d08efd6 Bump PHP minimum requirement to 8.0
PHPStan failed on 7.4 after updating to 0.12.99, and I figured it was less hassle to just do this than fix the build. In any case, we stopped shipping 7.4 months ago, and warned at 3.22 release that 7.4 support would soon be dropped.
2021-10-09 20:09:42 +01:00
289553fa46 CS again 2021-10-09 19:50:07 +01:00
e38866c4ba phpstan 0.12.99 2021-10-09 19:33:43 +01:00
58a95f8836 Updated transitive composer dependencies 2021-10-09 19:18:32 +01:00
ccc881ee58 Switch to custom permission denied message
closes #4494
2021-10-09 00:57:15 +01:00
308d7c126a Fixed world data ::generate() functions putting level.dat in the wrong place if the world path didn't end with a / 2021-10-08 23:39:25 +01:00
4910250a81 Config: fixed writeList() 2021-10-07 21:47:09 +01:00
e0d2e24698 fix CS (again\!) 2021-10-07 21:19:44 +01:00
d5f02a0bf8 Config: expose APIs to parse/emit list configs 2021-10-07 21:18:42 +01:00
2a3a57c519 Enable parsing/emitting .properties without creating a Config object
this is useful when the contents are just going to get passed straight into a model, making Config object redundant anyway.
2021-10-07 20:53:15 +01:00
5115387feb fix CS (again) 2021-10-07 20:43:55 +01:00
dd0aaf59b5 MainLogger: Log exceptions as a single block message 2021-10-07 20:40:20 +01:00
a555f21b18 MainLogger: write messages before calling logger attachments 2021-10-07 20:32:02 +01:00
1be9b2f037 Config: drop packing of arrays
we don't handle arrays on decode, so there's no reason to support them on encode either.
2021-10-07 20:30:56 +01:00
32fd9879e5 fix CS 2021-10-07 20:16:54 +01:00
dc2e8e7e8f ServerConfigGroup: do not assume that values are always bool|string 2021-10-07 20:02:21 +01:00
847e24fc41 4.0.0-BETA5 is next 2021-10-06 23:49:30 +01:00
e032b8fe20 Server: fixed stats reporting checking a nonexistent pocketmine.yml property
this was originally worked around by 47f7af6739. However, that commit was just duct tape, and I never bothered to investigate if the config was being checked somewhere else.
Here's to a years-old bug finally getting fixed.
2021-10-06 22:23:41 +01:00
169 changed files with 3133 additions and 1515 deletions

View File

@ -238,11 +238,10 @@ jobs:
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Run git diff
run: git diff
- name: Fail job if changes were made
run: git diff --quiet
- name: Verify code is unchanged
run: |
git diff
git diff --quiet
codestyle:
name: Code Style checks

View File

@ -22,8 +22,6 @@
declare(strict_types=1);
const VERSIONS = [
"7.3",
"7.4",
"8.0"
];

View File

@ -2,13 +2,13 @@
## Pre-requisites
- A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell
- PHP 7.4 or newer available in your shell
- PHP 8.0 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-7.4-Aggregate)
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate)
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
@ -34,7 +34,7 @@ There is a bug in PHP that might cause an error which looks like this:
```
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
```
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 7.4.16 or 8.0.3.
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3.
## Running PocketMine-MP from source code
Run `src/PocketMine.php` using your preferred PHP binary.

View File

@ -25,18 +25,26 @@ namespace pocketmine\build\make_release;
use pocketmine\utils\VersionString;
use pocketmine\VersionInfo;
use function count;
use function array_keys;
use function array_map;
use function dirname;
use function fgets;
use function file_get_contents;
use function file_put_contents;
use function fwrite;
use function getopt;
use function is_string;
use function max;
use function preg_replace;
use function sleep;
use function sprintf;
use function str_pad;
use function strlen;
use function system;
use const STDERR;
use const STDIN;
use const STDOUT;
use const STR_PAD_LEFT;
require_once dirname(__DIR__) . '/vendor/autoload.php';
@ -60,22 +68,38 @@ function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev
file_put_contents($versionInfoPath, $versionInfo);
}
/**
* @param string[] $argv
* @phpstan-param list<string> $argv
*/
function main(array $argv) : void{
if(count($argv) < 2){
fwrite(STDERR, "Arguments: <channel> [release version] [next version]\n");
exit(1);
const ACCEPTED_OPTS = [
"current" => "Version to insert and tag",
"next" => "Version to put in the file after tagging",
"channel" => "Release channel to post this build into"
];
function main() : void{
$filteredOpts = [];
foreach(getopt("", ["current:", "next:", "channel:", "help"]) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
$maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS)));
foreach(ACCEPTED_OPTS as $acceptedName => $description){
fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n");
}
exit(0);
}
if(!is_string($optValue)){
fwrite(STDERR, "--$optName expects exactly 1 value\n");
exit(1);
}
$filteredOpts[$optName] = $optValue;
}
if(isset($argv[2])){
$currentVer = new VersionString($argv[2]);
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = VersionInfo::VERSION();
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
if(isset($argv[3])){
$nextVer = new VersionString($argv[3]);
if(isset($filteredOpts["next"])){
$nextVer = new VersionString($filteredOpts["next"]);
}else{
$nextVer = new VersionString(sprintf(
"%u.%u.%u",
@ -84,8 +108,10 @@ function main(array $argv) : void{
$currentVer->getPatch() + 1
));
}
$channel = $filteredOpts["channel"] ?? VersionInfo::BUILD_CHANNEL;
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
echo "please add appropriate notes to the changelog and press enter...";
fgets(STDIN);
system('git add "' . dirname(__DIR__) . '/changelogs"');
@ -95,10 +121,10 @@ function main(array $argv) : void{
exit(1);
}
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $argv[1]);
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
system('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"');
system('git tag ' . $currentVer->getBaseVersion());
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, "");
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
system('git add "' . $versionInfoPath . '"');
system('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"');
echo "pushing changes in 5 seconds\n";
@ -106,4 +132,4 @@ function main(array $argv) : void{
system('git push origin HEAD ' . $currentVer->getBaseVersion());
}
main($argv);
main();

12
changelogs/3.24.md Normal file
View File

@ -0,0 +1,12 @@
**For Minecraft: Bedrock Edition 1.17.30**
### 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.24.0
- PHP 8.0 is now required as a minimum.
- Fixed stats reporting checking the wrong `pocketmine.yml` property.
- Fixed `Projectile->move()` not respecting the given `dx`/`dy`/`dz` and using its own motion instead.

11
changelogs/3.25.md Normal file
View File

@ -0,0 +1,11 @@
**For Minecraft: Bedrock Edition 1.17.40**
### 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.25.0
- Added support for Minecraft: Bedrock Edition 1.17.40.
- Removed compatibility with earlier versions.

View File

@ -329,6 +329,8 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- The following methods have signature changes:
- `Entity->entityBaseTick()` is now `protected`.
- `Entity->move()` is now `protected`.
- `Entity->setPosition()` is now `protected` (use `Entity->teleport()` instead).
- `Entity->setPositionAndRotation()` is now `protected` (use `Entity->teleport()` instead).
- `Living->knockBack()` now accepts `float, float, float` (the first two parameters have been removed).
- `Living->getEffects()` now returns `EffectManager` instead of `Effect[]`.
- The following classes have been added:
@ -1145,7 +1147,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
#### Particles
- `DestroyBlockParticle` has been renamed to `BlockBreakParticle` for consistency.
- `DustParticle->__construct()` now accepts a `pocketmine\utils\Color` object instead of `r, g, b, a`.
- `DustParticle->__construct()` now accepts a `pocketmine\color\Color` object instead of `r, g, b, a`.
- `pocketmine\world\particle\Particle` no longer extends `pocketmine\math\Vector3`, and has been converted to an interface.
- Added the following `Particle` classes:
- `DragonEggTeleportParticle`
@ -1391,3 +1393,106 @@ Released 6th October 2021.
### Utils
- The following API methods have signature changes:
- `Process::kill()` now requires an additional `bool $subprocesses` parameter.
# 4.0.0-BETA5
Released 12th October 2021.
## General
- Exception log format has been changed. Now, exception info is logged in one big block message. This saves space on the console and improves readability, as well as reducing the ability for bad `ThreadedLoggerAttachment`s to break exception output.
- Log messages are now pushed to `server.log` before calling logger attachment, instead of after. This fixes messages not being written to disk when an error occurs in a logger attachment.
- Improved startup performance when loading many plugins.
- The `worlds` config in `pocketmine.yml` no longer supports attaching the generator settings to the `generator` key (use the `preset` key instead).
- Using an unknown generator in `server.properties` or `pocketmine.yml` will now cause a failure to generate the world.
- Using invalid/incorrect world generator options (presets) in `server.properties` or `pocketmine.yml` will now cause a failure to generate the world.
- Generator options of existing worlds are now validated before loading them. If they are invalid, the server will fail to load them.
- Several more log messages have been localized, including plugin loading errors.
## Fixes
- Fixed server crash when using `/give` to give an item by ID which doesn't exist in Minecraft.
- Fixed server crash when boolean `server.properties` options were given an integer value (e.g. `0` or `1` instead of `false` or `true`).
- Fixed stats reporting checking for a nonexistent `pocketmine.yml` setting.
- Fixed use of commands without the proper permission sending a message `commands.generic.permission` instead of the proper message.
- Fixed entities set on fire appearing to stay on fire, although not taking any damage.
- Fixed a duplicate `MB` suffix on the `Memory freed` output of `/gc`.
- Fixed the server attempting to generate a world if it failed to load.
## API
### Block
- The following API methods have been renamed:
- `Block->getPositionOffset()` -> `Block->getModelPositionOffset()`.
### Event
- `@handleCancelled` PhpDoc annotation can no longer be used on event handlers for non-cancellable events.
- The following API methods have been added:
- `StructureGrowEvent->getPlayer()`
### Inventory
- The following API methods have been added:
- `Inventory->getAddableItemQuantity()`
### Scheduler
- `ClosureTask` now permits closures without an explicit return type (useful for arrow functions).
### Utils
- The following API methods have been added:
- `Config::parseProperties()`
- `Config::writeProperties()`
- `Config::parseList()`
- `Config::writeList()`
### World
- The following API methods have signature changes:
- `GeneratorManager->registerGenerator()` now requires a `\Closure $presetValidator` parameter. This is used to check generator options of worlds and configs before attempting to use them.
# 4.0.0-BETA6
Released 19th October 2021.
## General
- Added support for Minecraft: Bedrock Edition 1.17.40.
- Removed support for earlier versions.
- CTRL+C signal handling has been restored, and is now supported on Windows. Pressing CTRL+C while the server is running will behave as if the `/stop` command was invoked.
- Added a script `tools/generate-permission-doc.php` to generate a Markdown file with a list of all permissions and their relationships. In the future, this will be used to maintain the official documentation, but plugin developers might find it useful for their own purposes too.
- [`respect/validation`](https://packagist.org/packages/respect/validation) is no longer a core dependency.
## Fixes
- Fixed server crash when using `/give` to give an item with a too-large item ID, or `/clear` to clear an item that does not exist.
- Now, `LegacyStringToItemParser` is used exclusively, and numeric IDs are no longer parsed.
## Gameplay
- Picking up some items from a dropped stack of items is now supported. This fixes various bugs with being unable to pick up items with an almost-full inventory.
# 4.0.0-BETA7
Released 28 October 2021.
## General
- Phar plugins are now able to depend on folder plugins loaded by DevTools.
- Now uses [`pocketmine/bedrock-protocol@58c53a259e819a076bf8fe875d2a012da7d19d65`](https://github.com/pmmp/BedrockProtocol/tree/58c53a259e819a076bf8fe875d2a012da7d19d65). This version features significant changes, including:
- Standardisation of various field names (e.g. `eid` -> `actorRuntimeId`, `evid` -> `eventId`)
- Rename of `entity` related fields to `actor` where appropriate (e.g. `entityRuntimeId` -> `actorRuntimeId`)
- Block position `x`/`y`/`z` fields replaced by `BlockPosition`
- Static `::create()` functions for all packets, which ensure that fields can't be forgotten
## Fixes
- Fixed server crash when clients send itemstacks with unmappable dynamic item IDs.
- Fixed server crash on invalid ItemStackRequest action types.
- Fixed autosave bug that caused unmodified chunks to be saved at least once (during the first autosave after they were loaded).
- Fixed `ConsoleReaderThread` returning strings with newlines still on the end.
- Fixed changes made to adjacent chunks in `ChunkPopulateEvent` (e.g. setting blocks) sometimes getting overwritten.
## API
### Event
- `PlayerCreationEvent` now verifies that the player class set is instantiable - this ensures that plugins get properly blamed for breaking things.
### World
- `World->generateChunkCallback()` has been specialized for use by `PopulationTask`. This allows fixing various inconsistencies involving `ChunkPopulateEvent` (e.g. modifications to adjacent chunks in `ChunkPopulationEvent` might be wiped out, if the population of the target chunk modified the adjacent chunk).
- It now accepts `Chunk $centerChunk, array<int, Chunk> $adjacentChunks` (instead of `?Chunk $chunk`).
- It's no longer expected to be used by plugins - plugins should be using `World->setChunk()` anyway.
- `Chunk->getModificationCounter()` has been added. This is a number starting from `0` when the `Chunk` object is first created (unless overridden by the constructor). It's incremented every time blocks or biomes are changed in the chunk. It resets after the chunk is unloaded and reloaded.
- The following API methods have changed signatures:
- `Sound->encode()` no longer accepts `null` for the position.
- `Chunk->__construct()`: removed `HeightArray $heightMap` parameter, added `bool $terrainPopulated` and `int $modificationCounter` parameters.
### Plugin
- `PluginManager->loadPlugins()` now accepts paths to files as well as directories, in which case it will load only the plugin found in the target file.
- The following API methods have been removed:
- `PluginManager->loadPlugin()`: use `PluginManager->loadPlugins()` instead

View File

@ -34,7 +34,7 @@
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-protocol": "2.0.0+bedrock1.17.30",
"pocketmine/bedrock-protocol": "dev-master",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "dev-master",
@ -49,11 +49,10 @@
"pocketmine/snooze": "^0.3.0",
"pocketmine/spl": "dev-master",
"ramsey/uuid": "^4.1",
"respect/validation": "^2.0",
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "0.12.98",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-phpunit": "^0.12.6",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^9.2"

287
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "77f899b35819bca38246a665fd0233f3",
"content-hash": "ddbcdbc7ea7247bea7fcb37e5c42340b",
"packages": [
{
"name": "adhocore/json-comment",
@ -249,22 +249,22 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "2.0.0+bedrock1.17.30",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "faff7da904e68f69b1a9128956dac3122e87308a"
"reference": "58c53a259e819a076bf8fe875d2a012da7d19d65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/faff7da904e68f69b1a9128956dac3122e87308a",
"reference": "faff7da904e68f69b1a9128956dac3122e87308a",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/58c53a259e819a076bf8fe875d2a012da7d19d65",
"reference": "58c53a259e819a076bf8fe875d2a012da7d19d65",
"shasum": ""
},
"require": {
"ext-json": "*",
"netresearch/jsonmapper": "^4.0",
"php": "^7.4 || ^8.0",
"php": "^8.0",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/math": "^0.3.0",
@ -277,6 +277,7 @@
"phpstan/phpstan-strict-rules": "^0.12.10",
"phpunit/phpunit": "^9.5"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@ -290,22 +291,22 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.17.30"
"source": "https://github.com/pmmp/BedrockProtocol/tree/master"
},
"time": "2021-09-21T23:25:51+00:00"
"time": "2021-10-27T19:49:20+00:00"
},
{
"name": "pocketmine/binaryutils",
"version": "0.2.1",
"version": "0.2.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "8cd078e2426f8100331f2d73bef10f481dad6cde"
"reference": "f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/8cd078e2426f8100331f2d73bef10f481dad6cde",
"reference": "8cd078e2426f8100331f2d73bef10f481dad6cde",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9",
"reference": "f883e1cf9099ed6a757a10a2f75b3333eeb2cdf9",
"shasum": ""
},
"require": {
@ -314,7 +315,7 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.85",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4"
},
"type": "library",
@ -330,9 +331,9 @@
"description": "Classes and methods for conveniently handling binary data",
"support": {
"issues": "https://github.com/pmmp/BinaryUtils/issues",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.1"
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.2"
},
"time": "2021-05-30T19:42:57+00:00"
"time": "2021-10-22T19:54:16+00:00"
},
{
"name": "pocketmine/callback-validator",
@ -836,16 +837,16 @@
},
{
"name": "ramsey/collection",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa"
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/eaca1dc1054ddd10cbd83c1461907bee6fb528fa",
"reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa",
"url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a",
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a",
"shasum": ""
},
"require": {
@ -899,7 +900,7 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/1.2.1"
"source": "https://github.com/ramsey/collection/tree/1.2.2"
},
"funding": [
{
@ -911,7 +912,7 @@
"type": "tidelift"
}
],
"time": "2021-08-06T03:41:06+00:00"
"time": "2021-10-10T03:01:02+00:00"
},
{
"name": "ramsey/uuid",
@ -1011,130 +1012,6 @@
],
"time": "2021-09-25T23:10:38+00:00"
},
{
"name": "respect/stringifier",
"version": "0.2.0",
"source": {
"type": "git",
"url": "https://github.com/Respect/Stringifier.git",
"reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Respect/Stringifier/zipball/e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59",
"reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.8",
"malukenho/docheader": "^0.1.7",
"phpunit/phpunit": "^6.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Respect\\Stringifier\\": "src/"
},
"files": [
"src/stringify.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Respect/Stringifier Contributors",
"homepage": "https://github.com/Respect/Stringifier/graphs/contributors"
}
],
"description": "Converts any value to a string",
"homepage": "http://respect.github.io/Stringifier/",
"keywords": [
"respect",
"stringifier",
"stringify"
],
"support": {
"issues": "https://github.com/Respect/Stringifier/issues",
"source": "https://github.com/Respect/Stringifier/tree/0.2.0"
},
"time": "2017-12-29T19:39:25+00:00"
},
{
"name": "respect/validation",
"version": "2.2.3",
"source": {
"type": "git",
"url": "https://github.com/Respect/Validation.git",
"reference": "4c21a7ffc9a4915673cb2c2843963919e664e627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Respect/Validation/zipball/4c21a7ffc9a4915673cb2c2843963919e664e627",
"reference": "4c21a7ffc9a4915673cb2c2843963919e664e627",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0",
"respect/stringifier": "^0.2.0",
"symfony/polyfill-mbstring": "^1.2"
},
"require-dev": {
"egulias/email-validator": "^3.0",
"malukenho/docheader": "^0.1",
"mikey179/vfsstream": "^1.6",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-deprecation-rules": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^9.3",
"psr/http-message": "^1.0",
"respect/coding-standard": "^3.0",
"squizlabs/php_codesniffer": "^3.5",
"symfony/validator": "^3.0||^4.0",
"zendframework/zend-validator": "^2.1"
},
"suggest": {
"egulias/email-validator": "Strict (RFC compliant) email validation",
"ext-bcmath": "Arbitrary Precision Mathematics",
"ext-fileinfo": "File Information",
"ext-mbstring": "Multibyte String Functions",
"symfony/validator": "Use Symfony validator through Respect\\Validation",
"zendframework/zend-validator": "Use Zend Framework validator through Respect\\Validation"
},
"type": "library",
"autoload": {
"psr-4": {
"Respect\\Validation\\": "library/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Respect/Validation Contributors",
"homepage": "https://github.com/Respect/Validation/graphs/contributors"
}
],
"description": "The most awesome validation engine ever created for PHP",
"homepage": "http://respect.github.io/Validation/",
"keywords": [
"respect",
"validation",
"validator"
],
"support": {
"issues": "https://github.com/Respect/Validation/issues",
"source": "https://github.com/Respect/Validation/tree/2.2.3"
},
"time": "2021-03-19T14:12:45+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.23.0",
@ -1214,86 +1091,6 @@
],
"time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
"reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-27T12:26:48+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.23.1",
@ -1915,16 +1712,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.2.2",
"version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"shasum": ""
},
"require": {
@ -1935,7 +1732,8 @@
"webmozart/assert": "^1.9.1"
},
"require-dev": {
"mockery/mockery": "~1.3.2"
"mockery/mockery": "~1.3.2",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@ -1965,22 +1763,22 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
},
"time": "2020-09-03T19:13:55+00:00"
"time": "2021-10-19T17:43:47+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.5.0",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f"
"reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f",
"reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"shasum": ""
},
"require": {
@ -2015,9 +1813,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
},
"time": "2021-09-17T15:28:14+00:00"
"time": "2021-10-02T14:08:47+00:00"
},
{
"name": "phpspec/prophecy",
@ -2088,16 +1886,16 @@
},
{
"name": "phpstan/phpstan",
"version": "0.12.98",
"version": "0.12.99",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00"
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/3bb7cc246c057405dd5e290c3ecc62ab51d57e00",
"reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7",
"reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7",
"shasum": ""
},
"require": {
@ -2128,7 +1926,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/0.12.98"
"source": "https://github.com/phpstan/phpstan/tree/0.12.99"
},
"funding": [
{
@ -2148,7 +1946,7 @@
"type": "tidelift"
}
],
"time": "2021-09-02T12:33:01+00:00"
"time": "2021-09-12T20:09:55+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -3693,6 +3491,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"pocketmine/bedrock-protocol": 20,
"pocketmine/classloader": 20,
"pocketmine/spl": 20
},

View File

@ -57,5 +57,5 @@ parameters:
#variadics don't work for this - mixed probably shouldn't work either, but for now it does
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that,
#we'll just fill it with 10 - it's very unlikely to encounter a callable with 10 parameters anyway.
anyCallable: 'callable(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed'
anyClosure: '\Closure(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed'
anyCallable: 'callable(never, never, never, never, never, never, never, never, never, never) : mixed'
anyClosure: '\Closure(never, never, never, never, never, never, never, never, never, never) : mixed'

View File

@ -1,4 +1,691 @@
{
"-2": -2,
"-3": -3,
"-4": -4,
"-5": -5,
"-6": -6,
"-7": -7,
"-8": -8,
"-9": -9,
"-10": -10,
"-11": -11,
"-12": -12,
"-13": -13,
"-14": -14,
"-15": -15,
"-16": -16,
"-17": -17,
"-18": -18,
"-19": -19,
"-20": -20,
"-21": -21,
"-22": -22,
"-23": -23,
"-24": -24,
"-25": -25,
"-26": -26,
"-27": -27,
"-28": -28,
"-29": -29,
"-30": -30,
"-31": -31,
"-32": -32,
"-33": -33,
"-34": -34,
"-35": -35,
"-36": -36,
"-37": -37,
"-38": -38,
"-39": -39,
"-40": -40,
"-41": -41,
"-42": -42,
"-43": -43,
"-44": -44,
"-45": -45,
"-46": -46,
"-47": -47,
"-48": -48,
"-49": -49,
"-50": -50,
"-51": -51,
"-52": -52,
"-53": -53,
"-54": -54,
"-55": -55,
"-56": -56,
"-57": -57,
"-58": -58,
"-59": -59,
"-60": -60,
"-61": -61,
"-62": -62,
"-63": -63,
"-64": -64,
"-65": -65,
"-66": -66,
"-67": -67,
"-68": -68,
"-69": -69,
"-70": -70,
"-71": -71,
"-72": -72,
"-73": -73,
"-74": -74,
"-75": -75,
"-76": -76,
"-77": -77,
"-78": -78,
"-79": -79,
"-80": -80,
"-81": -81,
"-82": -82,
"-83": -83,
"-84": -84,
"-85": -85,
"-86": -86,
"-87": -87,
"-88": -88,
"-89": -89,
"-90": -90,
"-91": -91,
"-92": -92,
"-93": -93,
"-94": -94,
"-95": -95,
"-96": -96,
"-97": -97,
"-98": -98,
"-99": -99,
"-100": -100,
"-101": -101,
"-102": -102,
"-103": -103,
"-104": -104,
"-105": -105,
"-106": -106,
"-107": -107,
"-108": -108,
"-109": -109,
"-110": -110,
"-111": -111,
"-112": -112,
"-113": -113,
"-114": -114,
"-115": -115,
"-116": -116,
"-117": -117,
"-118": -118,
"-119": -119,
"-120": -120,
"-121": -121,
"-122": -122,
"-123": -123,
"-124": -124,
"-125": -125,
"-126": -126,
"-127": -127,
"-128": -128,
"-129": -129,
"-130": -130,
"-131": -131,
"-132": -132,
"-133": -133,
"-134": -134,
"-135": -135,
"-136": -136,
"-137": -137,
"-138": -138,
"-139": -139,
"-140": -140,
"-141": -141,
"-142": -142,
"-143": -143,
"-144": -144,
"-145": -145,
"-146": -146,
"-147": -147,
"-148": -148,
"-149": -149,
"-150": -150,
"-151": -151,
"-152": -152,
"-153": -153,
"-154": -154,
"-155": -155,
"-156": -156,
"-157": -157,
"-159": -159,
"-160": -160,
"-161": -161,
"-162": -162,
"-163": -163,
"-164": -164,
"-165": -165,
"-166": -166,
"-167": -167,
"-168": -168,
"-169": -169,
"-170": -170,
"-171": -171,
"-172": -172,
"-173": -173,
"-174": -174,
"-175": -175,
"-176": -176,
"-177": -177,
"-178": -178,
"-179": -179,
"-180": -180,
"-181": -181,
"-182": -182,
"-183": -183,
"-184": -184,
"-185": -185,
"-186": -186,
"-187": -187,
"-188": -188,
"-189": -189,
"-190": -190,
"-191": -191,
"-192": -192,
"-193": -193,
"-194": -194,
"-195": -195,
"-196": -196,
"-197": -197,
"-198": -198,
"-199": -199,
"-200": -200,
"-201": -201,
"-202": -202,
"-203": -203,
"-204": -204,
"-206": -206,
"-207": -207,
"-208": -208,
"-209": -209,
"-210": -210,
"-211": -211,
"-213": -213,
"-214": -214,
"0": 0,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"10": 10,
"11": 11,
"12": 12,
"13": 13,
"14": 14,
"15": 15,
"16": 16,
"17": 17,
"18": 18,
"19": 19,
"20": 20,
"21": 21,
"22": 22,
"23": 23,
"24": 24,
"25": 25,
"26": 26,
"27": 27,
"28": 28,
"29": 29,
"30": 30,
"31": 31,
"32": 32,
"33": 33,
"34": 34,
"35": 35,
"36": 36,
"37": 37,
"38": 38,
"39": 39,
"40": 40,
"41": 41,
"42": 42,
"43": 43,
"44": 44,
"45": 45,
"46": 46,
"47": 47,
"48": 48,
"49": 49,
"50": 50,
"51": 51,
"52": 52,
"53": 53,
"54": 54,
"55": 55,
"56": 56,
"57": 57,
"58": 58,
"59": 59,
"60": 60,
"61": 61,
"62": 62,
"63": 63,
"64": 64,
"65": 65,
"66": 66,
"67": 67,
"68": 68,
"69": 69,
"70": 70,
"71": 71,
"72": 72,
"73": 73,
"74": 74,
"75": 75,
"76": 76,
"77": 77,
"78": 78,
"79": 79,
"80": 80,
"81": 81,
"82": 82,
"83": 83,
"84": 84,
"85": 85,
"86": 86,
"87": 87,
"88": 88,
"89": 89,
"90": 90,
"91": 91,
"92": 92,
"93": 93,
"94": 94,
"95": 95,
"96": 96,
"97": 97,
"98": 98,
"99": 99,
"100": 100,
"101": 101,
"102": 102,
"103": 103,
"104": 104,
"105": 105,
"106": 106,
"107": 107,
"108": 108,
"109": 109,
"110": 110,
"111": 111,
"112": 112,
"113": 113,
"114": 114,
"115": 115,
"116": 116,
"117": 117,
"118": 118,
"119": 119,
"120": 120,
"121": 121,
"122": 122,
"123": 123,
"124": 124,
"125": 125,
"126": 126,
"127": 127,
"128": 128,
"129": 129,
"130": 130,
"131": 131,
"132": 132,
"133": 133,
"134": 134,
"135": 135,
"136": 136,
"137": 137,
"138": 138,
"139": 139,
"140": 140,
"141": 141,
"142": 142,
"143": 143,
"144": 144,
"145": 145,
"146": 146,
"147": 147,
"148": 148,
"149": 149,
"150": 150,
"151": 151,
"152": 152,
"153": 153,
"154": 154,
"155": 155,
"156": 156,
"157": 157,
"158": 158,
"159": 159,
"160": 160,
"161": 161,
"162": 162,
"163": 163,
"164": 164,
"165": 165,
"166": 166,
"167": 167,
"168": 168,
"169": 169,
"170": 170,
"171": 171,
"172": 172,
"173": 173,
"174": 174,
"175": 175,
"176": 176,
"177": 177,
"178": 178,
"179": 179,
"180": 180,
"181": 181,
"182": 182,
"183": 183,
"184": 184,
"185": 185,
"186": 186,
"187": 187,
"188": 188,
"189": 189,
"190": 190,
"191": 191,
"192": 192,
"193": 193,
"194": 194,
"195": 195,
"196": 196,
"197": 197,
"198": 198,
"199": 199,
"200": 200,
"201": 201,
"202": 202,
"203": 203,
"204": 204,
"205": 205,
"206": 206,
"207": 207,
"208": 208,
"209": 209,
"213": 213,
"214": 214,
"215": 215,
"216": 216,
"218": 218,
"219": 219,
"220": 220,
"221": 221,
"222": 222,
"223": 223,
"224": 224,
"225": 225,
"226": 226,
"227": 227,
"228": 228,
"229": 229,
"231": 231,
"232": 232,
"233": 233,
"234": 234,
"235": 235,
"236": 236,
"237": 237,
"238": 238,
"239": 239,
"240": 240,
"241": 241,
"243": 243,
"244": 244,
"245": 245,
"246": 246,
"247": 247,
"248": 248,
"249": 249,
"250": 250,
"251": 251,
"252": 252,
"253": 253,
"254": 254,
"255": 255,
"256": 256,
"257": 257,
"258": 258,
"259": 259,
"260": 260,
"261": 261,
"262": 262,
"263": 263,
"264": 264,
"265": 265,
"266": 266,
"267": 267,
"268": 268,
"269": 269,
"270": 270,
"271": 271,
"272": 272,
"273": 273,
"274": 274,
"275": 275,
"276": 276,
"277": 277,
"278": 278,
"279": 279,
"280": 280,
"281": 281,
"282": 282,
"283": 283,
"284": 284,
"285": 285,
"286": 286,
"287": 287,
"288": 288,
"289": 289,
"290": 290,
"291": 291,
"292": 292,
"293": 293,
"294": 294,
"295": 295,
"296": 296,
"297": 297,
"298": 298,
"299": 299,
"300": 300,
"301": 301,
"302": 302,
"303": 303,
"304": 304,
"305": 305,
"306": 306,
"307": 307,
"308": 308,
"309": 309,
"310": 310,
"311": 311,
"312": 312,
"313": 313,
"314": 314,
"315": 315,
"316": 316,
"317": 317,
"318": 318,
"319": 319,
"320": 320,
"321": 321,
"322": 322,
"323": 323,
"324": 324,
"325": 325,
"328": 328,
"329": 329,
"330": 330,
"331": 331,
"332": 332,
"333": 333,
"334": 334,
"335": 335,
"336": 336,
"337": 337,
"338": 338,
"339": 339,
"340": 340,
"341": 341,
"342": 342,
"344": 344,
"345": 345,
"346": 346,
"347": 347,
"348": 348,
"349": 349,
"350": 350,
"351": 351,
"352": 352,
"353": 353,
"354": 354,
"355": 355,
"356": 356,
"357": 357,
"358": 358,
"359": 359,
"360": 360,
"361": 361,
"362": 362,
"363": 363,
"364": 364,
"365": 365,
"366": 366,
"367": 367,
"368": 368,
"369": 369,
"370": 370,
"371": 371,
"372": 372,
"373": 373,
"374": 374,
"375": 375,
"376": 376,
"377": 377,
"378": 378,
"379": 379,
"380": 380,
"381": 381,
"382": 382,
"383": 383,
"384": 384,
"385": 385,
"386": 386,
"387": 387,
"388": 388,
"389": 389,
"390": 390,
"391": 391,
"392": 392,
"393": 393,
"394": 394,
"395": 395,
"396": 396,
"397": 397,
"398": 398,
"399": 399,
"400": 400,
"401": 401,
"402": 402,
"403": 403,
"404": 404,
"405": 405,
"406": 406,
"407": 407,
"408": 408,
"409": 409,
"410": 410,
"411": 411,
"412": 412,
"413": 413,
"414": 414,
"415": 415,
"416": 416,
"417": 417,
"418": 418,
"419": 419,
"420": 420,
"421": 421,
"422": 422,
"423": 423,
"424": 424,
"425": 425,
"426": 426,
"427": 427,
"428": 428,
"429": 429,
"430": 430,
"431": 431,
"432": 432,
"433": 433,
"434": 434,
"437": 437,
"438": 438,
"441": 441,
"442": 442,
"443": 443,
"444": 444,
"445": 445,
"446": 446,
"447": 447,
"448": 448,
"449": 449,
"450": 450,
"451": 451,
"452": 452,
"453": 453,
"455": 455,
"457": 457,
"458": 458,
"459": 459,
"460": 460,
"461": 461,
"462": 462,
"463": 463,
"464": 464,
"465": 465,
"466": 466,
"467": 467,
"468": 468,
"469": 469,
"470": 470,
"471": 471,
"472": 472,
"473": 473,
"474": 474,
"475": 475,
"476": 476,
"477": 477,
"499": 499,
"500": 500,
"501": 501,
"502": 502,
"503": 503,
"504": 504,
"505": 505,
"506": 506,
"507": 507,
"508": 508,
"509": 509,
"510": 510,
"511": 511,
"513": 513,
"acacia_button": -140,
"acacia_door": 430,
"acacia_door_block": 196,
@ -226,27 +913,16 @@
"egg": 344,
"element_0": 36,
"element_1": -12,
"element_2": -13,
"element_3": -14,
"element_4": -15,
"element_5": -16,
"element_6": -17,
"element_7": -18,
"element_8": -19,
"element_9": -20,
"element_10": -21,
"element_100": -111,
"element_101": -112,
"element_102": -113,
"element_103": -114,
"element_104": -115,
"element_105": -116,
"element_106": -117,
"element_107": -118,
"element_108": -119,
"element_109": -120,
"element_11": -22,
"element_110": -121,
"element_111": -122,
"element_112": -123,
"element_113": -124,
"element_114": -125,
"element_115": -126,
"element_116": -127,
"element_117": -128,
"element_118": -129,
"element_12": -23,
"element_13": -24,
"element_14": -25,
@ -255,7 +931,6 @@
"element_17": -28,
"element_18": -29,
"element_19": -30,
"element_2": -13,
"element_20": -31,
"element_21": -32,
"element_22": -33,
@ -266,7 +941,6 @@
"element_27": -38,
"element_28": -39,
"element_29": -40,
"element_3": -14,
"element_30": -41,
"element_31": -42,
"element_32": -43,
@ -277,7 +951,6 @@
"element_37": -48,
"element_38": -49,
"element_39": -50,
"element_4": -15,
"element_40": -51,
"element_41": -52,
"element_42": -53,
@ -288,7 +961,6 @@
"element_47": -58,
"element_48": -59,
"element_49": -60,
"element_5": -16,
"element_50": -61,
"element_51": -62,
"element_52": -63,
@ -299,7 +971,6 @@
"element_57": -68,
"element_58": -69,
"element_59": -70,
"element_6": -17,
"element_60": -71,
"element_61": -72,
"element_62": -73,
@ -310,7 +981,6 @@
"element_67": -78,
"element_68": -79,
"element_69": -80,
"element_7": -18,
"element_70": -81,
"element_71": -82,
"element_72": -83,
@ -321,7 +991,6 @@
"element_77": -88,
"element_78": -89,
"element_79": -90,
"element_8": -19,
"element_80": -91,
"element_81": -92,
"element_82": -93,
@ -332,7 +1001,6 @@
"element_87": -98,
"element_88": -99,
"element_89": -100,
"element_9": -20,
"element_90": -101,
"element_91": -102,
"element_92": -103,
@ -343,6 +1011,25 @@
"element_97": -108,
"element_98": -109,
"element_99": -110,
"element_100": -111,
"element_101": -112,
"element_102": -113,
"element_103": -114,
"element_104": -115,
"element_105": -116,
"element_106": -117,
"element_107": -118,
"element_108": -119,
"element_109": -120,
"element_110": -121,
"element_111": -122,
"element_112": -123,
"element_113": -124,
"element_114": -125,
"element_115": -126,
"element_116": -127,
"element_117": -128,
"element_118": -129,
"elytra": 444,
"emerald": 388,
"emerald_block": 133,
@ -532,8 +1219,8 @@
"leather_pants": 300,
"leather_tunic": 299,
"leave": 18,
"leaves": 18,
"leave2": 161,
"leaves": 18,
"leaves2": 161,
"lectern": -194,
"lever": 69,

View File

@ -101,9 +101,12 @@ class CrashDump{
/** @var string */
private $path;
public function __construct(Server $server){
private ?PluginManager $pluginManager;
public function __construct(Server $server, ?PluginManager $pluginManager){
$this->time = microtime(true);
$this->server = $server;
$this->pluginManager = $pluginManager;
$crashPath = Path::join($this->server->getDataPath(), "crashdumps");
if(!is_dir($crashPath)){
@ -166,11 +169,11 @@ class CrashDump{
}
private function pluginsData() : void{
if($this->server->getPluginManager() instanceof PluginManager){
if($this->pluginManager !== null){
$plugins = $this->pluginManager->getPlugins();
$this->addLine();
$this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
$plugins = $this->server->getPluginManager()->getPlugins();
ksort($plugins, SORT_STRING);
foreach($plugins as $p){
$d = $p->getDescription();

View File

@ -98,6 +98,7 @@ use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable;
use pocketmine\utils\Process;
use pocketmine\utils\Promise;
use pocketmine\utils\SignalHandler;
use pocketmine\utils\Terminal;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
@ -106,24 +107,22 @@ use pocketmine\world\format\io\WorldProviderManager;
use pocketmine\world\format\io\WritableWorldProviderManagerEntry;
use pocketmine\world\generator\Generator;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\InvalidGeneratorOptionsException;
use pocketmine\world\World;
use pocketmine\world\WorldCreationOptions;
use pocketmine\world\WorldManager;
use Ramsey\Uuid\UuidInterface;
use Webmozart\PathUtil\Path;
use function array_shift;
use function array_sum;
use function base64_encode;
use function cli_set_process_title;
use function copy;
use function count;
use function explode;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function get_class;
use function implode;
use function ini_set;
use function is_array;
use function is_string;
@ -252,6 +251,8 @@ class Server{
/** @var Player[] */
private array $playerList = [];
private SignalHandler $signalHandler;
/**
* @var CommandSender[][]
* @phpstan-var array<string, array<int, CommandSender>>
@ -745,6 +746,11 @@ class Server{
$this->autoloader = $autoloader;
$this->logger = $logger;
$this->signalHandler = new SignalHandler(function() : void{
$this->logger->info("Received signal interrupt, stopping the server");
$this->shutdown();
});
try{
foreach([
$dataPath,
@ -967,91 +973,16 @@ class Server{
$this->pluginManager->loadPlugins($this->pluginPath);
$this->enablePlugins(PluginEnableOrder::STARTUP());
foreach((array) $this->configGroup->getProperty("worlds", []) as $name => $options){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->worldManager->loadWorld($name, true)){
$creationOptions = WorldCreationOptions::create();
//TODO: error checking
if(isset($options["generator"])){
$generatorOptions = explode(":", $options["generator"]);
$creationOptions->setGeneratorClass(GeneratorManager::getInstance()->getGenerator(array_shift($generatorOptions)));
if(count($generatorOptions) > 0){
$creationOptions->setGeneratorOptions(implode(":", $generatorOptions));
}
}
if(isset($options["difficulty"]) && is_string($options["difficulty"])){
$creationOptions->setDifficulty(World::getDifficultyFromString($options["difficulty"]));
}
if(isset($options["preset"]) && is_string($options["preset"])){
$creationOptions->setGeneratorOptions($options["preset"]);
}
if(isset($options["seed"])){
$convertedSeed = Generator::convertSeed((string) ($options["seed"] ?? ""));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
}
$this->worldManager->generateWorld($name, $creationOptions);
}
if(!$this->startupPrepareWorlds()){
return;
}
if($this->worldManager->getDefaultWorld() === null){
$default = $this->configGroup->getConfigString("level-name", "world");
if(trim($default) == ""){
$this->getLogger()->warning("level-name cannot be null, using default");
$default = "world";
$this->configGroup->setConfigString("level-name", "world");
}
if(!$this->worldManager->loadWorld($default, true)){
$creationOptions = WorldCreationOptions::create()
->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($this->configGroup->getConfigString("level-type")))
->setGeneratorOptions($this->configGroup->getConfigString("generator-settings"));
$convertedSeed = Generator::convertSeed($this->configGroup->getConfigString("level-seed"));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
$this->worldManager->generateWorld($default, $creationOptions);
}
$world = $this->worldManager->getWorldByName($default);
if($world === null){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->forceShutdown();
return;
}
$this->worldManager->setDefaultWorld($world);
}
$this->enablePlugins(PluginEnableOrder::POSTWORLD());
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
if(!$this->network->registerInterface(new RakLibInterface($this)) && $useQuery){
//RakLib would normally handle the transport for Query packets
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
}
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($this->getIp(), (string) $this->getPort())));
if($useQuery){
$this->network->registerRawPacketHandler(new QueryHandler($this));
if(!$this->startupPrepareNetworkInterfaces()){
return;
}
foreach($this->getIPBans()->getEntries() as $entry){
$this->network->blockAddress($entry->getName(), -1);
}
if($this->configGroup->getPropertyBool("network.upnp-forwarding", false)){
$this->network->registerInterface(new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
}
if($this->configGroup->getPropertyBool("settings.send-usage", true)){
if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
$this->sendUsageTicker = 6000;
$this->sendUsage(SendUsageTask::TYPE_OPEN);
}
@ -1086,6 +1017,123 @@ class Server{
}
}
private function startupPrepareWorlds() : bool{
$getGenerator = function(string $generatorName, string $generatorOptions, string $worldName) : ?string{
$generatorEntry = GeneratorManager::getInstance()->getGenerator($generatorName);
if($generatorEntry === null){
$this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
$worldName,
KnownTranslationFactory::pocketmine_level_unknownGenerator($generatorName)
)));
return null;
}
try{
$generatorEntry->validateGeneratorOptions($generatorOptions);
}catch(InvalidGeneratorOptionsException $e){
$this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
$worldName,
KnownTranslationFactory::pocketmine_level_invalidGeneratorOptions($generatorOptions, $generatorName, $e->getMessage())
)));
return null;
}
return $generatorEntry->getGeneratorClass();
};
foreach((array) $this->configGroup->getProperty("worlds", []) as $name => $options){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->worldManager->loadWorld($name, true) && !$this->worldManager->isWorldGenerated($name)){
$creationOptions = WorldCreationOptions::create();
//TODO: error checking
$generatorName = $options["generator"] ?? "default";
$generatorOptions = isset($options["preset"]) && is_string($options["preset"]) ? $options["preset"] : "";
$generatorClass = $getGenerator($generatorName, $generatorOptions, $name);
if($generatorClass === null){
continue;
}
$creationOptions->setGeneratorClass($generatorClass);
$creationOptions->setGeneratorOptions($generatorOptions);
if(isset($options["difficulty"]) && is_string($options["difficulty"])){
$creationOptions->setDifficulty(World::getDifficultyFromString($options["difficulty"]));
}
if(isset($options["seed"])){
$convertedSeed = Generator::convertSeed((string) ($options["seed"] ?? ""));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
}
$this->worldManager->generateWorld($name, $creationOptions);
}
}
if($this->worldManager->getDefaultWorld() === null){
$default = $this->configGroup->getConfigString("level-name", "world");
if(trim($default) == ""){
$this->getLogger()->warning("level-name cannot be null, using default");
$default = "world";
$this->configGroup->setConfigString("level-name", "world");
}
if(!$this->worldManager->loadWorld($default, true) && !$this->worldManager->isWorldGenerated($default)){
$generatorName = $this->configGroup->getConfigString("level-type");
$generatorOptions = $this->configGroup->getConfigString("generator-settings");
$generatorClass = $getGenerator($generatorName, $generatorOptions, $default);
if($generatorClass !== null){
$creationOptions = WorldCreationOptions::create()
->setGeneratorClass($generatorClass)
->setGeneratorOptions($generatorOptions);
$convertedSeed = Generator::convertSeed($this->configGroup->getConfigString("level-seed"));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
$this->worldManager->generateWorld($default, $creationOptions);
}
}
$world = $this->worldManager->getWorldByName($default);
if($world === null){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->forceShutdown();
return false;
}
$this->worldManager->setDefaultWorld($world);
}
return true;
}
private function startupPrepareNetworkInterfaces() : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
if(!$this->network->registerInterface(new RakLibInterface($this)) && $useQuery){
//RakLib would normally handle the transport for Query packets
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
}
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($this->getIp(), (string) $this->getPort())));
if($useQuery){
$this->network->registerRawPacketHandler(new QueryHandler($this));
}
foreach($this->getIPBans()->getEntries() as $entry){
$this->network->blockAddress($entry->getName(), -1);
}
if($this->configGroup->getPropertyBool("network.upnp-forwarding", false)){
$this->network->registerInterface(new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
}
return true;
}
/**
* Subscribes to a particular message broadcast channel.
* The channel ID can be any arbitrary string.
@ -1301,7 +1349,10 @@ class Server{
* Shuts the server down correctly
*/
public function shutdown() : void{
$this->isRunning = false;
if($this->isRunning){
$this->isRunning = false;
$this->signalHandler->unregister();
}
}
public function forceShutdown() : void{
@ -1425,7 +1476,7 @@ class Server{
ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems
try{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this);
$dump = new CrashDump($this, $this->pluginManager ?? null);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($dump->getPath())));

View File

@ -27,6 +27,8 @@ use pocketmine\utils\Config;
use function array_key_exists;
use function getopt;
use function is_bool;
use function is_int;
use function is_string;
use function strtolower;
final class ServerConfigGroup{
@ -110,16 +112,20 @@ final class ServerConfigGroup{
}else{
$value = $this->serverProperties->exists($variable) ? $this->serverProperties->get($variable) : $defaultValue;
}
if(is_bool($value)){
return $value;
}
switch(strtolower($value)){
case "on":
case "true":
case "1":
case "yes":
return true;
if(is_int($value)){
return $value !== 0;
}
if(is_string($value)){
switch(strtolower($value)){
case "on":
case "true":
case "1":
case "yes":
return true;
}
}
return false;

View File

@ -29,7 +29,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA4";
public const BASE_VERSION = "4.0.0-BETA7";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_NUMBER = 0;
public const BUILD_CHANNEL = "beta";

View File

@ -115,7 +115,7 @@ class Bamboo extends Transparent{
return 12 + (self::getOffsetSeed($x, 0, $z) % 5);
}
public function getPositionOffset() : ?Vector3{
public function getModelPositionOffset() : ?Vector3{
$seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ());
$retX = (($seed % 12) + 1) / 16;
$retZ = ((($seed >> 8) % 12) + 1) / 16;
@ -145,12 +145,12 @@ class Bamboo extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$top = $this->seekToTop();
if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2))){
if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2), $player)){
$item->pop();
return true;
}
}elseif($item instanceof ItemBamboo){
if($this->seekToTop()->grow(PHP_INT_MAX, 1)){
if($this->seekToTop()->grow(PHP_INT_MAX, 1, $player)){
$item->pop();
return true;
}
@ -165,7 +165,7 @@ class Bamboo extends Transparent{
}
}
private function grow(int $maxHeight, int $growAmount) : bool{
private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false;
@ -212,7 +212,7 @@ class Bamboo extends Transparent{
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
}
$ev = new StructureGrowEvent($this, $tx);
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
if($ev->isCancelled()){
return false;
@ -229,7 +229,7 @@ class Bamboo extends Transparent{
$world = $this->position->getWorld();
if($this->ready){
$this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1)){
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1, null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -73,7 +73,7 @@ final class BambooSapling extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer || $item instanceof ItemBamboo){
if($this->grow()){
if($this->grow($player)){
$item->pop();
return true;
}
@ -87,7 +87,7 @@ final class BambooSapling extends Flowable{
}
}
private function grow() : bool{
private function grow(?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false;
@ -98,7 +98,7 @@ final class BambooSapling extends Flowable{
$tx->addBlock($this->position, $bamboo)
->addBlock($this->position->up(), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES));
$ev = new StructureGrowEvent($this, $tx);
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
if($ev->isCancelled()){
return false;
@ -115,7 +115,7 @@ final class BambooSapling extends Flowable{
$world = $this->position->getWorld();
if($this->ready){
$this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow()){
if($world->getFullLight($this->position) < 9 || !$this->grow(null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -577,7 +577,7 @@ class Block{
final public function getCollisionBoxes() : array{
if($this->collisionBoxes === null){
$this->collisionBoxes = $this->recalculateCollisionBoxes();
$extraOffset = $this->getPositionOffset();
$extraOffset = $this->getModelPositionOffset();
$offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
foreach($this->collisionBoxes as $bb){
$bb->offset($offset->x, $offset->y, $offset->z);
@ -588,10 +588,10 @@ class Block{
}
/**
* Returns an additional fractional vector to shift the block's effective position by based on the current position.
* Returns an additional fractional vector to shift the block model's position by based on the current position.
* Used to randomize position of things like bamboo canes and tall grass.
*/
public function getPositionOffset() : ?Vector3{
public function getModelPositionOffset() : ?Vector3{
return null;
}

View File

@ -183,6 +183,7 @@ class BlockFactory{
$this->register(new EnderChest(new BID(Ids::ENDER_CHEST, 0, null, TileEnderChest::class), "Ender Chest", new BlockBreakInfo(22.5, BlockToolType::PICKAXE, ToolTier::WOOD()->getHarvestLevel(), 3000.0)));
$this->register(new Farmland(new BID(Ids::FARMLAND, 0), "Farmland", new BlockBreakInfo(0.6, BlockToolType::SHOVEL)));
$this->register(new Fire(new BID(Ids::FIRE, 0), "Fire Block", BlockBreakInfo::instant()));
$this->register(new FletchingTable(new BID(Ids::FLETCHING_TABLE, 0), "Fletching Table", new BlockBreakInfo(2.5, BlockToolType::AXE, 0, 2.5)));
$this->register(new Flower(new BID(Ids::DANDELION, 0), "Dandelion", BlockBreakInfo::instant()));
$this->register(new Flower(new BID(Ids::RED_FLOWER, Meta::FLOWER_ALLIUM), "Allium", BlockBreakInfo::instant()));
$this->register(new Flower(new BID(Ids::RED_FLOWER, Meta::FLOWER_AZURE_BLUET), "Azure Bluet", BlockBreakInfo::instant()));
@ -402,6 +403,7 @@ class BlockFactory{
$this->registerSlabWithDoubleHighBitsRemapping(new Slab(BlockLegacyIdHelper::getStoneSlabIdentifier(4, Meta::STONE_SLAB4_STONE), "Stone", $stoneSlabBreakInfo));
$this->register(new Opaque(new BID(Ids::STONECUTTER, 0), "Stonecutter", new BlockBreakInfo(3.5, BlockToolType::PICKAXE, ToolTier::WOOD()->getHarvestLevel())));
$this->register(new Sugarcane(new BID(Ids::REEDS_BLOCK, 0, ItemIds::REEDS), "Sugarcane", BlockBreakInfo::instant()));
$this->register(new SweetBerryBush(new BID(Ids::SWEET_BERRY_BUSH, 0, ItemIds::SWEET_BERRIES), "Sweet Berry Bush", BlockBreakInfo::instant()));
$this->register(new TNT(new BID(Ids::TNT, 0), "TNT", BlockBreakInfo::instant()));
$fern = new TallGrass(new BID(Ids::TALLGRASS, Meta::TALLGRASS_FERN), "Fern", BlockBreakInfo::instant(BlockToolType::SHEARS, 1));
@ -567,7 +569,6 @@ class BlockFactory{
//TODO: minecraft:dropper
//TODO: minecraft:end_gateway
//TODO: minecraft:end_portal
//TODO: minecraft:fletching_table
//TODO: minecraft:grindstone
//TODO: minecraft:jigsaw
//TODO: minecraft:kelp
@ -584,7 +585,6 @@ class BlockFactory{
//TODO: minecraft:sticky_piston
//TODO: minecraft:stonecutter_block
//TODO: minecraft:structure_block
$this->register(new SweetBerryBush(new BID(Ids::SWEET_BERRY_BUSH, 0, ItemIds::SWEET_BERRIES), "Sweet Berry Bush", BlockBreakInfo::instant()));
//TODO: minecraft:turtle_egg
//endregion

View File

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

View File

@ -77,7 +77,7 @@ class Sapling extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$this->grow();
$this->grow($player);
$item->pop();
@ -100,7 +100,7 @@ class Sapling extends Flowable{
public function onRandomTick() : void{
if($this->position->getWorld()->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 and mt_rand(1, 7) === 1){
if($this->ready){
$this->grow();
$this->grow(null);
}else{
$this->ready = true;
$this->position->getWorld()->setBlock($this->position, $this);
@ -108,7 +108,7 @@ class Sapling extends Flowable{
}
}
private function grow() : void{
private function grow(?Player $player) : void{
$random = new Random(mt_rand());
$tree = TreeFactory::get($random, $this->treeType);
$transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random);
@ -116,13 +116,11 @@ class Sapling extends Flowable{
return;
}
$ev = new StructureGrowEvent($this, $transaction);
$ev = new StructureGrowEvent($this, $transaction, $player);
$ev->call();
if($ev->isCancelled()){
return;
if(!$ev->isCancelled()){
$transaction->apply();
}
$transaction->apply();
}
public function getFuelTime() : int{

View File

@ -300,6 +300,7 @@ use function assert;
* @method static Farmland FARMLAND()
* @method static TallGrass FERN()
* @method static Fire FIRE()
* @method static FletchingTable FLETCHING_TABLE()
* @method static FlowerPot FLOWER_POT()
* @method static FrostedIce FROSTED_ICE()
* @method static Furnace FURNACE()
@ -866,6 +867,7 @@ final class VanillaBlocks{
self::register("farmland", $factory->get(60, 0));
self::register("fern", $factory->get(31, 2));
self::register("fire", $factory->get(51, 0));
self::register("fletching_table", $factory->get(456, 0));
self::register("flower_pot", $factory->get(140, 0));
self::register("frosted_ice", $factory->get(207, 0));
self::register("furnace", $factory->get(61, 2));

View File

@ -25,6 +25,7 @@ namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
@ -50,6 +51,6 @@ class ChestInventory extends SimpleInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -28,6 +28,7 @@ use pocketmine\inventory\DelegateInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerEnderInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\player\Player;
use pocketmine\world\Position;
use pocketmine\world\sound\EnderChestCloseSound;
@ -74,7 +75,7 @@ class EnderChestInventory extends DelegateInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
public function onClose(Player $who) : void{

View File

@ -27,6 +27,7 @@ use pocketmine\block\BlockLegacyIds;
use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ShulkerBoxCloseSound;
use pocketmine\world\sound\ShulkerBoxOpenSound;
@ -59,6 +60,6 @@ class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(1, $isOpen ? 1 : 0, $holder->asVector3()));
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\math\Facing;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
final class Bell extends Spawnable{
@ -81,6 +82,6 @@ final class Bell extends Spawnable{
$nbt->setByte(self::TAG_RINGING, 1);
$nbt->setInt(self::TAG_DIRECTION, BlockDataSerializer::writeLegacyHorizontalFacing($bellHitFace));
$nbt->setInt(self::TAG_TICKS, 0);
return BlockActorDataPacket::create($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), new CacheableNbt($nbt));
return BlockActorDataPacket::create(BlockPosition::fromVector3($this->position), new CacheableNbt($nbt));
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\block\utils;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
use pocketmine\utils\EnumTrait;
/**
@ -54,18 +54,18 @@ final class RecordType{
protected static function setup() : void{
self::registerAll(
new RecordType("disk_13", "C418 - 13", LevelSoundEventPacket::SOUND_RECORD_13, KnownTranslationFactory::item_record_13_desc()),
new RecordType("disk_cat", "C418 - cat", LevelSoundEventPacket::SOUND_RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()),
new RecordType("disk_blocks", "C418 - blocks", LevelSoundEventPacket::SOUND_RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()),
new RecordType("disk_chirp", "C418 - chirp", LevelSoundEventPacket::SOUND_RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()),
new RecordType("disk_far", "C418 - far", LevelSoundEventPacket::SOUND_RECORD_FAR, KnownTranslationFactory::item_record_far_desc()),
new RecordType("disk_mall", "C418 - mall", LevelSoundEventPacket::SOUND_RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()),
new RecordType("disk_mellohi", "C418 - mellohi", LevelSoundEventPacket::SOUND_RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()),
new RecordType("disk_stal", "C418 - stal", LevelSoundEventPacket::SOUND_RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()),
new RecordType("disk_strad", "C418 - strad", LevelSoundEventPacket::SOUND_RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()),
new RecordType("disk_ward", "C418 - ward", LevelSoundEventPacket::SOUND_RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()),
new RecordType("disk_11", "C418 - 11", LevelSoundEventPacket::SOUND_RECORD_11, KnownTranslationFactory::item_record_11_desc()),
new RecordType("disk_wait", "C418 - wait", LevelSoundEventPacket::SOUND_RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc())
new RecordType("disk_13", "C418 - 13", LevelSoundEvent::RECORD_13, KnownTranslationFactory::item_record_13_desc()),
new RecordType("disk_cat", "C418 - cat", LevelSoundEvent::RECORD_CAT, KnownTranslationFactory::item_record_cat_desc()),
new RecordType("disk_blocks", "C418 - blocks", LevelSoundEvent::RECORD_BLOCKS, KnownTranslationFactory::item_record_blocks_desc()),
new RecordType("disk_chirp", "C418 - chirp", LevelSoundEvent::RECORD_CHIRP, KnownTranslationFactory::item_record_chirp_desc()),
new RecordType("disk_far", "C418 - far", LevelSoundEvent::RECORD_FAR, KnownTranslationFactory::item_record_far_desc()),
new RecordType("disk_mall", "C418 - mall", LevelSoundEvent::RECORD_MALL, KnownTranslationFactory::item_record_mall_desc()),
new RecordType("disk_mellohi", "C418 - mellohi", LevelSoundEvent::RECORD_MELLOHI, KnownTranslationFactory::item_record_mellohi_desc()),
new RecordType("disk_stal", "C418 - stal", LevelSoundEvent::RECORD_STAL, KnownTranslationFactory::item_record_stal_desc()),
new RecordType("disk_strad", "C418 - strad", LevelSoundEvent::RECORD_STRAD, KnownTranslationFactory::item_record_strad_desc()),
new RecordType("disk_ward", "C418 - ward", LevelSoundEvent::RECORD_WARD, KnownTranslationFactory::item_record_ward_desc()),
new RecordType("disk_11", "C418 - 11", LevelSoundEvent::RECORD_11, KnownTranslationFactory::item_record_11_desc()),
new RecordType("disk_wait", "C418 - wait", LevelSoundEvent::RECORD_WAIT, KnownTranslationFactory::item_record_wait_desc())
//TODO: Lena Raine - Pigstep
);
}

View File

@ -117,7 +117,7 @@ abstract class Command{
}
if($this->permissionMessage === null){
$target->sendMessage(KnownTranslationFactory::commands_generic_permission()->prefix(TextFormat::RED));
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->prefix(TextFormat::RED));
}elseif($this->permissionMessage !== ""){
$target->sendMessage(str_replace("<permission>", $permission ?? $this->permission, $this->permissionMessage));
}

View File

@ -68,7 +68,7 @@ class GarbageCollectorCommand extends VanillaCommand{
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_cycles(TextFormat::RED . number_format($cyclesCollected))->prefix(TextFormat::GOLD));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_memoryFreed(TextFormat::RED . number_format(round((($memory - memory_get_usage()) / 1024) / 1024, 2), 2) . " MB")->prefix(TextFormat::GOLD));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_memoryFreed(TextFormat::RED . number_format(round((($memory - memory_get_usage()) / 1024) / 1024, 2), 2))->prefix(TextFormat::GOLD));
return true;
}
}

View File

@ -39,6 +39,7 @@ use function stream_socket_accept;
use function stream_socket_get_name;
use function stream_socket_server;
use function stream_socket_shutdown;
use function trim;
use const PHP_BINARY;
use const STREAM_SHUT_RDWR;
@ -113,7 +114,7 @@ final class ConsoleReaderThread extends Thread{
break;
}
$buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $command);
$buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command));
if($notifier !== null){
$notifier->wakeupSleeper();
}

View File

@ -681,10 +681,12 @@ abstract class Entity{
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
}
$this->fireTicks = $fireTicks;
$this->networkPropertiesDirty = true;
}
public function extinguish() : void{
$this->fireTicks = 0;
$this->networkPropertiesDirty = true;
}
public function isFireProof() : bool{
@ -1416,20 +1418,21 @@ abstract class Entity{
* Called by spawnTo() to send whatever packets needed to spawn the entity to the client.
*/
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddActorPacket();
$pk->entityRuntimeId = $this->getId();
$pk->type = static::getNetworkTypeId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->headYaw = $this->location->yaw; //TODO
$pk->pitch = $this->location->pitch;
$pk->attributes = array_map(function(Attribute $attr) : NetworkAttribute{
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
}, $this->attributeMap->getAll());
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddActorPacket::create(
$this->getId(), //TODO: actor unique ID
$this->getId(),
static::getNetworkTypeId(),
$this->location->asVector3(),
$this->getMotion(),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw, //TODO: head yaw
array_map(function(Attribute $attr) : NetworkAttribute{
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
}, $this->attributeMap->getAll()),
$this->getAllNetworkData(),
[] //TODO: entity links
));
}
public function spawnTo(Player $player) : void{

View File

@ -47,8 +47,10 @@ use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\types\DeviceOS;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
@ -145,7 +147,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
*/
public function sendSkin(?array $targets = null) : void{
$this->server->broadcastPackets($targets ?? $this->hasSpawned, [
PlayerSkinPacket::create($this->getUniqueId(), SkinAdapterSingleton::get()->toSkinData($this->skin))
PlayerSkinPacket::create($this->getUniqueId(), "", "", SkinAdapterSingleton::get()->toSkinData($this->skin))
]);
}
@ -444,17 +446,24 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$player->getNetworkSession()->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))]));
}
$pk = new AddPlayerPacket();
$pk->uuid = $this->getUniqueId();
$pk->username = $this->getName();
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->pitch = $this->location->pitch;
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddPlayerPacket::create(
$this->getUniqueId(),
$this->getName(),
$this->getId(), //TODO: actor unique ID
$this->getId(),
"",
$this->location->asVector3(),
$this->getMotion(),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw, //TODO: head yaw
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand())),
$this->getAllNetworkData(),
AdventureSettingsPacket::create(0, 0, 0, 0, 0, $this->getId()), //TODO
[], //TODO: entity links
"", //device ID (we intentionally don't send this - secvuln)
DeviceOS::UNKNOWN //we intentionally don't send this (secvuln)
));
//TODO: Hack for MCPE 1.2.13: DATA_NAMETAG is useless in AddPlayerPacket, so it has to be sent separately
$this->sendData([$player], [EntityMetadataProperties::NAMETAG => new StringMetadataProperty($this->getNameTag())]);

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class ArmSwingAnimation implements Animation{
@ -37,7 +38,7 @@ final class ArmSwingAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::ARM_SWING, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::ARM_SWING, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\projectile\Arrow;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
class ArrowShakeAnimation implements Animation{
@ -40,7 +41,7 @@ class ArrowShakeAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->arrow->getId(), ActorEventPacket::ARROW_SHAKE, $this->durationInTicks)
ActorEventPacket::create($this->arrow->getId(), ActorEvent::ARROW_SHAKE, $this->durationInTicks)
];
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\entity\Human;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class ConsumingItemAnimation implements Animation{
@ -45,7 +46,7 @@ final class ConsumingItemAnimation implements Animation{
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($this->item->getId(), $this->item->getMeta());
return [
//TODO: need to check the data values
ActorEventPacket::create($this->human->getId(), ActorEventPacket::EATING_ITEM, ($netId << 16) | $netData)
ActorEventPacket::create($this->human->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class DeathAnimation implements Animation{
@ -37,7 +38,7 @@ final class DeathAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::DEATH_ANIMATION, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::DEATH_ANIMATION, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class HurtAnimation implements Animation{
@ -37,7 +38,7 @@ final class HurtAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::HURT_ANIMATION, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::HURT_ANIMATION, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class RespawnAnimation implements Animation{
@ -37,7 +38,7 @@ final class RespawnAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->entity->getId(), ActorEventPacket::RESPAWN, 0)
ActorEventPacket::create($this->entity->getId(), ActorEvent::RESPAWN, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Squid;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class SquidInkCloudAnimation implements Animation{
@ -37,7 +38,7 @@ final class SquidInkCloudAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->squid->getId(), ActorEventPacket::SQUID_INK_CLOUD, 0)
ActorEventPacket::create($this->squid->getId(), ActorEvent::SQUID_INK_CLOUD, 0)
];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\entity\animation;
use pocketmine\entity\Human;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
final class TotemUseAnimation implements Animation{
@ -38,7 +39,7 @@ final class TotemUseAnimation implements Animation{
public function encode() : array{
return [
ActorEventPacket::create($this->human->getId(), ActorEventPacket::CONSUME_TOTEM, 0)
ActorEventPacket::create($this->human->getId(), ActorEvent::CONSUME_TOTEM, 0)
];
}
}

View File

@ -206,14 +206,15 @@ class ItemEntity extends Entity{
}
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddItemActorPacket();
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getItem()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddItemActorPacket::create(
$this->getId(), //TODO: entity unique ID
$this->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getItem())),
$this->location->asVector3(),
$this->getMotion(),
$this->getAllNetworkData(),
false //TODO: I have no idea what this is needed for, but right now we don't support fishing anyway
));
}
public function getOffsetPosition(Vector3 $vector3) : Vector3{
@ -227,8 +228,8 @@ class ItemEntity extends Entity{
$item = $this->getItem();
$playerInventory = match(true){
$player->getOffHandInventory()->getItem(0)->canStackWith($item) and $player->getOffHandInventory()->canAddItem($item) => $player->getOffHandInventory(),
$player->getInventory()->canAddItem($item) => $player->getInventory(),
$player->getOffHandInventory()->getItem(0)->canStackWith($item) and $player->getOffHandInventory()->getAddableItemQuantity($item) > 0 => $player->getOffHandInventory(),
$player->getInventory()->getAddableItemQuantity($item) > 0 => $player->getInventory(),
default => null
};
@ -246,7 +247,12 @@ class ItemEntity extends Entity{
$viewer->getNetworkSession()->onPlayerPickUpItem($player, $this);
}
$ev->getInventory()?->addItem($ev->getItem());
$inventory = $ev->getInventory();
if($inventory !== null){
foreach($inventory->addItem($ev->getItem()) as $remains){
$this->getWorld()->dropItem($this->location, $remains, new Vector3(0, 0, 0));
}
}
$this->flagForDespawn();
}
}

View File

@ -149,17 +149,17 @@ class Painting extends Entity{
}
protected function sendSpawnPacket(Player $player) : void{
$pk = new AddPaintingPacket();
$pk->entityRuntimeId = $this->getId();
$pk->position = new Vector3(
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
($this->boundingBox->minY + $this->boundingBox->maxY) / 2,
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2
);
$pk->direction = self::FACING_TO_DATA[$this->facing];
$pk->title = $this->motive->getName();
$player->getNetworkSession()->sendDataPacket($pk);
$player->getNetworkSession()->sendDataPacket(AddPaintingPacket::create(
$this->getId(), //TODO: entity unique ID
$this->getId(),
new Vector3(
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
($this->boundingBox->minY + $this->boundingBox->maxY) / 2,
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2
),
self::FACING_TO_DATA[$this->facing],
$this->motive->getName()
));
}
/**

View File

@ -7,6 +7,7 @@ namespace pocketmine\event\block;
use pocketmine\block\Block;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
/**
@ -17,13 +18,23 @@ class StructureGrowEvent extends BlockEvent implements Cancellable{
use CancellableTrait;
private BlockTransaction $transaction;
private ?Player $player;
public function __construct(Block $block, BlockTransaction $transaction){
public function __construct(Block $block, BlockTransaction $transaction, ?Player $player){
parent::__construct($block);
$this->transaction = $transaction;
$this->player = $player;
}
public function getTransaction() : BlockTransaction{
return $this->transaction;
}
/**
* It returns the player which grows the structure.
* It returns null when the structure grows by itself.
*/
public function getPlayer() : ?Player{
return $this->player;
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\event\player;
use pocketmine\event\Event;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function is_a;
/**
@ -96,10 +97,7 @@ class PlayerCreationEvent extends Event{
* @phpstan-param class-string<Player> $class
*/
public function setPlayerClass($class) : void{
if(!is_a($class, $this->baseClass, true)){
throw new \RuntimeException("Class $class must extend " . $this->baseClass);
}
Utils::testValidInstance($class, $this->baseClass);
$this->playerClass = $class;
}
}

View File

@ -169,6 +169,10 @@ abstract class BaseInventory implements Inventory{
}
public function canAddItem(Item $item) : bool{
return $this->getAddableItemQuantity($item) === $item->getCount();
}
public function getAddableItemQuantity(Item $item) : int{
$count = $item->getCount();
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
@ -181,11 +185,11 @@ abstract class BaseInventory implements Inventory{
}
if($count <= 0){
return true;
return $item->getCount();
}
}
return false;
return $item->getCount() - $count;
}
public function addItem(Item ...$slots) : array{

View File

@ -63,6 +63,11 @@ interface Inventory{
*/
public function canAddItem(Item $item) : bool;
/**
* Returns how many items from the given itemstack can be added to this inventory.
*/
public function getAddableItemQuantity(Item $item) : int;
/**
* Removes the given Item from the inventory.
* It will return the Items that couldn't be removed.

View File

@ -31,7 +31,6 @@ use function file_get_contents;
use function is_array;
use function is_int;
use function is_numeric;
use function is_string;
use function json_decode;
use function str_replace;
use function strtolower;
@ -63,8 +62,8 @@ final class LegacyStringToItemParser{
if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array");
foreach($mappings as $name => $id){
if(!is_string($name) or !is_int($id)) throw new AssumptionFailedError("Invalid mappings format, expected string keys and int values");
$result->addMapping($name, $id);
if(!is_int($id)) throw new AssumptionFailedError("Invalid mappings format, expected int values");
$result->addMapping((string) $name, $id);
}
return $result;
@ -84,6 +83,14 @@ final class LegacyStringToItemParser{
$this->map[$alias] = $id;
}
/**
* @return int[]
* @phpstan-return array<string, int>
*/
public function getMappings() : array{
return $this->map;
}
/**
* Tries to parse the specified string into Item types.
*
@ -106,9 +113,7 @@ final class LegacyStringToItemParser{
throw new LegacyStringToItemParserException("Unable to parse \"" . $b[1] . "\" from \"" . $input . "\" as a valid meta value");
}
if(is_numeric($b[0])){
$item = $this->itemFactory->get((int) $b[0], $meta);
}elseif(isset($this->map[strtolower($b[0])])){
if(isset($this->map[strtolower($b[0])])){
$item = $this->itemFactory->get($this->map[strtolower($b[0])], $meta);
}else{
throw new LegacyStringToItemParserException("Unable to resolve \"" . $input . "\" to a valid item");

View File

@ -530,6 +530,7 @@ final class StringToItemParser{
$result->registerBlock("fence_gate_spruce", fn() => VanillaBlocks::SPRUCE_FENCE_GATE());
$result->registerBlock("fern", fn() => VanillaBlocks::FERN());
$result->registerBlock("fire", fn() => VanillaBlocks::FIRE());
$result->registerBlock("fletching_table", fn() => VanillaBlocks::FLETCHING_TABLE());
$result->registerBlock("flower_pot", fn() => VanillaBlocks::FLOWER_POT());
$result->registerBlock("flower_pot_block", fn() => VanillaBlocks::FLOWER_POT());
$result->registerBlock("flowing_lava", fn() => VanillaBlocks::LAVA());

View File

@ -1109,6 +1109,12 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ENCHANT_DESCRIPTION, []);
}
public static function pocketmine_command_error_permission(Translatable|string $commandName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ERROR_PERMISSION, [
"commandName" => $commandName,
]);
}
public static function pocketmine_command_error_playerNotFound(Translatable|string $playerName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ERROR_PLAYERNOTFOUND, [
"playerName" => $playerName,
@ -1569,6 +1575,14 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_level_invalidGeneratorOptions(Translatable|string $preset, Translatable|string $generatorName, Translatable|string $details) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_LEVEL_INVALIDGENERATOROPTIONS, [
"preset" => $preset,
"generatorName" => $generatorName,
"details" => $details,
]);
}
public static function pocketmine_level_loadError(Translatable|string $param0, Translatable|string $param1) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_LEVEL_LOADERROR, [
0 => $param0,
@ -1665,6 +1679,12 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_plugin_badDataFolder(Translatable|string $dataFolder) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_BADDATAFOLDER, [
"dataFolder" => $dataFolder,
]);
}
public static function pocketmine_plugin_circularDependency() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_CIRCULARDEPENDENCY, []);
}
@ -1690,18 +1710,39 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_plugin_disallowedByBlacklist() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_DISALLOWEDBYBLACKLIST, []);
}
public static function pocketmine_plugin_disallowedByWhitelist() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_DISALLOWEDBYWHITELIST, []);
}
public static function pocketmine_plugin_duplicateError(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_DUPLICATEERROR, [
0 => $param0,
]);
}
public static function pocketmine_plugin_emptyExtensionVersionConstraint(Translatable|string $constraintIndex, Translatable|string $extensionName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT, [
"constraintIndex" => $constraintIndex,
"extensionName" => $extensionName,
]);
}
public static function pocketmine_plugin_enable(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_ENABLE, [
0 => $param0,
]);
}
public static function pocketmine_plugin_extensionNotLoaded(Translatable|string $extensionName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EXTENSIONNOTLOADED, [
"extensionName" => $extensionName,
]);
}
public static function pocketmine_plugin_genericLoadError(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_GENERICLOADERROR, [
0 => $param0,
@ -1714,6 +1755,14 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_plugin_incompatibleExtensionVersion(Translatable|string $extensionVersion, Translatable|string $extensionName, Translatable|string $pluginRequirement) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_INCOMPATIBLEEXTENSIONVERSION, [
"extensionVersion" => $extensionVersion,
"extensionName" => $extensionName,
"pluginRequirement" => $pluginRequirement,
]);
}
public static function pocketmine_plugin_incompatibleOS(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_INCOMPATIBLEOS, [
0 => $param0,
@ -1738,6 +1787,13 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_plugin_invalidExtensionVersionConstraint(Translatable|string $versionConstraint, Translatable|string $extensionName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_INVALIDEXTENSIONVERSIONCONSTRAINT, [
"versionConstraint" => $versionConstraint,
"extensionName" => $extensionName,
]);
}
public static function pocketmine_plugin_invalidManifest(Translatable|string $details) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_INVALIDMANIFEST, [
"details" => $details,
@ -1757,6 +1813,16 @@ final class KnownTranslationFactory{
]);
}
public static function pocketmine_plugin_mainClassNotFound() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_MAINCLASSNOTFOUND, []);
}
public static function pocketmine_plugin_mainClassWrongType(Translatable|string $pluginInterface) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_MAINCLASSWRONGTYPE, [
"pluginInterface" => $pluginInterface,
]);
}
public static function pocketmine_plugin_restrictedName() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_RESTRICTEDNAME, []);
}
@ -2009,6 +2075,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POTION_SATURATION, []);
}
public static function potion_slowFalling() : Translatable{
return new Translatable(KnownTranslationKeys::POTION_SLOWFALLING, []);
}
public static function potion_waterBreathing() : Translatable{
return new Translatable(KnownTranslationKeys::POTION_WATERBREATHING, []);
}

View File

@ -244,6 +244,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION = "pocketmine.command.difficulty.description";
public const POCKETMINE_COMMAND_EFFECT_DESCRIPTION = "pocketmine.command.effect.description";
public const POCKETMINE_COMMAND_ENCHANT_DESCRIPTION = "pocketmine.command.enchant.description";
public const POCKETMINE_COMMAND_ERROR_PERMISSION = "pocketmine.command.error.permission";
public const POCKETMINE_COMMAND_ERROR_PLAYERNOTFOUND = "pocketmine.command.error.playerNotFound";
public const POCKETMINE_COMMAND_EXCEPTION = "pocketmine.command.exception";
public const POCKETMINE_COMMAND_GAMEMODE_DESCRIPTION = "pocketmine.command.gamemode.description";
@ -337,6 +338,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_LEVEL_CORRUPTED = "pocketmine.level.corrupted";
public const POCKETMINE_LEVEL_DEFAULTERROR = "pocketmine.level.defaultError";
public const POCKETMINE_LEVEL_GENERATIONERROR = "pocketmine.level.generationError";
public const POCKETMINE_LEVEL_INVALIDGENERATOROPTIONS = "pocketmine.level.invalidGeneratorOptions";
public const POCKETMINE_LEVEL_LOADERROR = "pocketmine.level.loadError";
public const POCKETMINE_LEVEL_NOTFOUND = "pocketmine.level.notFound";
public const POCKETMINE_LEVEL_PREPARING = "pocketmine.level.preparing";
@ -351,21 +353,30 @@ final class KnownTranslationKeys{
public const POCKETMINE_PLAYER_LOGOUT = "pocketmine.player.logOut";
public const POCKETMINE_PLUGIN_ALIASERROR = "pocketmine.plugin.aliasError";
public const POCKETMINE_PLUGIN_AMBIGUOUSMINAPI = "pocketmine.plugin.ambiguousMinAPI";
public const POCKETMINE_PLUGIN_BADDATAFOLDER = "pocketmine.plugin.badDataFolder";
public const POCKETMINE_PLUGIN_CIRCULARDEPENDENCY = "pocketmine.plugin.circularDependency";
public const POCKETMINE_PLUGIN_COMMANDERROR = "pocketmine.plugin.commandError";
public const POCKETMINE_PLUGIN_DEPRECATEDEVENT = "pocketmine.plugin.deprecatedEvent";
public const POCKETMINE_PLUGIN_DISABLE = "pocketmine.plugin.disable";
public const POCKETMINE_PLUGIN_DISALLOWEDBYBLACKLIST = "pocketmine.plugin.disallowedByBlacklist";
public const POCKETMINE_PLUGIN_DISALLOWEDBYWHITELIST = "pocketmine.plugin.disallowedByWhitelist";
public const POCKETMINE_PLUGIN_DUPLICATEERROR = "pocketmine.plugin.duplicateError";
public const POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT = "pocketmine.plugin.emptyExtensionVersionConstraint";
public const POCKETMINE_PLUGIN_ENABLE = "pocketmine.plugin.enable";
public const POCKETMINE_PLUGIN_EXTENSIONNOTLOADED = "pocketmine.plugin.extensionNotLoaded";
public const POCKETMINE_PLUGIN_GENERICLOADERROR = "pocketmine.plugin.genericLoadError";
public const POCKETMINE_PLUGIN_INCOMPATIBLEAPI = "pocketmine.plugin.incompatibleAPI";
public const POCKETMINE_PLUGIN_INCOMPATIBLEEXTENSIONVERSION = "pocketmine.plugin.incompatibleExtensionVersion";
public const POCKETMINE_PLUGIN_INCOMPATIBLEOS = "pocketmine.plugin.incompatibleOS";
public const POCKETMINE_PLUGIN_INCOMPATIBLEPHPVERSION = "pocketmine.plugin.incompatiblePhpVersion";
public const POCKETMINE_PLUGIN_INCOMPATIBLEPROTOCOL = "pocketmine.plugin.incompatibleProtocol";
public const POCKETMINE_PLUGIN_INVALIDAPI = "pocketmine.plugin.invalidAPI";
public const POCKETMINE_PLUGIN_INVALIDEXTENSIONVERSIONCONSTRAINT = "pocketmine.plugin.invalidExtensionVersionConstraint";
public const POCKETMINE_PLUGIN_INVALIDMANIFEST = "pocketmine.plugin.invalidManifest";
public const POCKETMINE_PLUGIN_LOAD = "pocketmine.plugin.load";
public const POCKETMINE_PLUGIN_LOADERROR = "pocketmine.plugin.loadError";
public const POCKETMINE_PLUGIN_MAINCLASSNOTFOUND = "pocketmine.plugin.mainClassNotFound";
public const POCKETMINE_PLUGIN_MAINCLASSWRONGTYPE = "pocketmine.plugin.mainClassWrongType";
public const POCKETMINE_PLUGIN_RESTRICTEDNAME = "pocketmine.plugin.restrictedName";
public const POCKETMINE_PLUGIN_SPACESDISCOURAGED = "pocketmine.plugin.spacesDiscouraged";
public const POCKETMINE_PLUGIN_UNKNOWNDEPENDENCY = "pocketmine.plugin.unknownDependency";
@ -419,6 +430,7 @@ final class KnownTranslationKeys{
public const POTION_REGENERATION = "potion.regeneration";
public const POTION_RESISTANCE = "potion.resistance";
public const POTION_SATURATION = "potion.saturation";
public const POTION_SLOWFALLING = "potion.slowFalling";
public const POTION_WATERBREATHING = "potion.waterBreathing";
public const POTION_WEAKNESS = "potion.weakness";
public const POTION_WITHER = "potion.wither";

View File

@ -45,6 +45,7 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
@ -164,26 +165,27 @@ class InventoryManager{
//TODO: we should be using some kind of tagging system to identify the types. Instanceof is flaky especially
//if the class isn't final, not to mention being inflexible.
if($inv instanceof BlockInventory){
$blockPosition = BlockPosition::fromVector3($inv->getHolder());
switch(true){
case $inv instanceof LoomInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::LOOM, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::LOOM, $blockPosition)];
case $inv instanceof FurnaceInventory:
return match($inv->getFurnaceType()->id()){
FurnaceType::FURNACE()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::FURNACE, $inv->getHolder())],
FurnaceType::BLAST_FURNACE()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::BLAST_FURNACE, $inv->getHolder())],
FurnaceType::SMOKER()->id() => [ContainerOpenPacket::blockInvVec3($id, WindowTypes::SMOKER, $inv->getHolder())],
FurnaceType::FURNACE()->id() => [ContainerOpenPacket::blockInv($id, WindowTypes::FURNACE, $blockPosition)],
FurnaceType::BLAST_FURNACE()->id() => [ContainerOpenPacket::blockInv($id, WindowTypes::BLAST_FURNACE, $blockPosition)],
FurnaceType::SMOKER()->id() => [ContainerOpenPacket::blockInv($id, WindowTypes::SMOKER, $blockPosition)],
default => throw new AssumptionFailedError("Unreachable")
};
case $inv instanceof EnchantInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ENCHANTMENT, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::ENCHANTMENT, $blockPosition)];
case $inv instanceof BrewingStandInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::BREWING_STAND, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::BREWING_STAND, $blockPosition)];
case $inv instanceof AnvilInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ANVIL, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::ANVIL, $blockPosition)];
case $inv instanceof HopperInventory:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::HOPPER, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::HOPPER, $blockPosition)];
default:
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::CONTAINER, $inv->getHolder())];
return [ContainerOpenPacket::blockInv($id, WindowTypes::CONTAINER, $blockPosition)];
}
}
return null;
@ -279,6 +281,7 @@ class InventoryManager{
$this->player->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand())),
$selected,
$selected,
ContainerIds::INVENTORY
));
$this->clientSelectedHotbarSlot = $selected;

View File

@ -88,6 +88,7 @@ use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\command\CommandData;
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
@ -726,16 +727,17 @@ class NetworkSession{
$yaw = $yaw ?? $location->getYaw();
$pitch = $pitch ?? $location->getPitch();
$pk = new MovePlayerPacket();
$pk->entityRuntimeId = $this->player->getId();
$pk->position = $this->player->getOffsetPosition($pos);
$pk->pitch = $pitch;
$pk->headYaw = $yaw;
$pk->yaw = $yaw;
$pk->mode = $mode;
$pk->onGround = $this->player->onGround;
$this->sendDataPacket($pk);
$this->sendDataPacket(MovePlayerPacket::simple(
$this->player->getId(),
$this->player->getOffsetPosition($pos),
$pitch,
$yaw,
$yaw, //TODO: head yaw
$mode,
$this->player->onGround,
0, //TODO: riding entity ID
0 //TODO: tick
));
if($this->handler instanceof InGamePacketHandler){
$this->handler->forceMoveSync = true;
@ -748,16 +750,17 @@ class NetworkSession{
}
public function syncViewAreaCenterPoint(Vector3 $newPos, int $viewDistance) : void{
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create($newPos->getFloorX(), $newPos->getFloorY(), $newPos->getFloorZ(), $viewDistance * 16)); //blocks, not chunks >.>
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16)); //blocks, not chunks >.>
}
public function syncPlayerSpawnPoint(Position $newSpawn) : void{
[$x, $y, $z] = [$newSpawn->getFloorX(), $newSpawn->getFloorY(), $newSpawn->getFloorZ()];
$this->sendDataPacket(SetSpawnPositionPacket::playerSpawn($x, $y, $z, DimensionIds::OVERWORLD, $x, $y, $z));
$newSpawnBlockPosition = BlockPosition::fromVector3($newSpawn);
//TODO: respawn causing block position (bed, respawn anchor)
$this->sendDataPacket(SetSpawnPositionPacket::playerSpawn($newSpawnBlockPosition, DimensionIds::OVERWORLD, $newSpawnBlockPosition));
}
public function syncWorldSpawnPoint(Position $newSpawn) : void{
$this->sendDataPacket(SetSpawnPositionPacket::worldSpawn($newSpawn->getFloorX(), $newSpawn->getFloorY(), $newSpawn->getFloorZ(), DimensionIds::OVERWORLD));
$this->sendDataPacket(SetSpawnPositionPacket::worldSpawn(BlockPosition::fromVector3($newSpawn), DimensionIds::OVERWORLD));
}
public function syncGameMode(GameMode $mode, bool $isRollback = false) : void{
@ -774,7 +777,15 @@ class NetworkSession{
* TODO: make this less specialized
*/
public function syncAdventureSettings(Player $for) : void{
$pk = new AdventureSettingsPacket();
$isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
$pk = AdventureSettingsPacket::create(
0,
$isOp ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL,
0,
$isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
0,
$for->getId()
);
$pk->setFlag(AdventureSettingsPacket::WORLD_IMMUTABLE, $for->isSpectator());
$pk->setFlag(AdventureSettingsPacket::NO_PVP, $for->isSpectator());
@ -785,11 +796,6 @@ class NetworkSession{
//TODO: permission flags
$isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
$pk->commandPermission = ($isOp ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL);
$pk->playerPermission = ($isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER);
$pk->entityUniqueId = $for->getId();
$this->sendDataPacket($pk);
}
@ -826,9 +832,9 @@ class NetworkSession{
}
public function syncAvailableCommands() : void{
$pk = new AvailableCommandsPacket();
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
if(isset($pk->commandData[$command->getName()]) or $command->getName() === "help" or !$command->testPermissionSilent($this->player)){
if(isset($commandData[$command->getName()]) or $command->getName() === "help" or !$command->testPermissionSilent($this->player)){
continue;
}
@ -855,10 +861,10 @@ class NetworkSession{
]
);
$pk->commandData[$command->getName()] = $data;
$commandData[$command->getName()] = $data;
}
$this->sendDataPacket($pk);
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));
}
public function onRawChatMessage(string $message) : void{
@ -966,12 +972,12 @@ class NetworkSession{
public function onMobMainHandItemChange(Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
}
public function onMobOffHandItemChange(Human $mob) : void{
$inv = $mob->getOffHandInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, ContainerIds::OFFHAND));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, 0, ContainerIds::OFFHAND));
}
public function onMobArmorChange(Living $mob) : void{

View File

@ -71,15 +71,14 @@ final class CraftingDataCache{
*/
private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{
Timings::$craftingDataCacheRebuild->startTiming();
$pk = new CraftingDataPacket();
$pk->cleanRecipes = true;
$counter = 0;
$nullUUID = Uuid::fromString(Uuid::NIL);
$converter = TypeConverter::getInstance();
$recipesWithTypeIds = [];
foreach($manager->getShapelessRecipes() as $list){
foreach($list as $recipe){
$pk->entries[] = new ProtocolShapelessRecipe(
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
Binary::writeInt(++$counter),
array_map(function(Item $item) use ($converter) : RecipeIngredient{
@ -104,7 +103,7 @@ final class CraftingDataCache{
$inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row));
}
}
$pk->entries[] = $r = new ProtocolShapedRecipe(
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
CraftingDataPacket::ENTRY_SHAPED,
Binary::writeInt(++$counter),
$inputs,
@ -128,7 +127,7 @@ final class CraftingDataCache{
};
foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){
$input = $converter->coreItemStackToNet($recipe->getInput());
$pk->entries[] = new ProtocolFurnaceRecipe(
$recipesWithTypeIds[] = new ProtocolFurnaceRecipe(
CraftingDataPacket::ENTRY_FURNACE_DATA,
$input->getId(),
$input->getMeta(),
@ -139,6 +138,6 @@ final class CraftingDataCache{
}
Timings::$craftingDataCacheRebuild->stopTiming();
return $pk;
return CraftingDataPacket::create($recipesWithTypeIds, [], [], [], true);
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\data\bedrock\LegacyItemIdToStringIdMap;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
@ -76,10 +77,7 @@ final class ItemTranslator{
if($legacyStringToIntMapRaw === false){
throw new AssumptionFailedError("Missing required resource file");
}
$legacyStringToIntMap = json_decode($legacyStringToIntMapRaw, true);
if(!is_array($legacyStringToIntMap)){
throw new AssumptionFailedError("Invalid mapping table format");
}
$legacyStringToIntMap = LegacyItemIdToStringIdMap::getInstance();
/** @phpstan-var array<string, int> $simpleMappings */
$simpleMappings = [];
@ -87,13 +85,14 @@ final class ItemTranslator{
if(!is_string($oldId) || !is_string($newId)){
throw new AssumptionFailedError("Invalid item table format");
}
if(!isset($legacyStringToIntMap[$oldId])){
$intId = $legacyStringToIntMap->stringToLegacy($oldId);
if($intId === null){
//new item without a fixed legacy ID - we can't handle this right now
continue;
}
$simpleMappings[$newId] = $legacyStringToIntMap[$oldId];
$simpleMappings[$newId] = $intId;
}
foreach($legacyStringToIntMap as $stringId => $intId){
foreach($legacyStringToIntMap->getStringToLegacyMap() as $stringId => $intId){
if(isset($simpleMappings[$stringId])){
throw new \UnexpectedValueException("Old ID $stringId collides with new ID");
}
@ -110,7 +109,12 @@ final class ItemTranslator{
if(!is_numeric($meta) || !is_string($newId)){
throw new AssumptionFailedError("Invalid item table format");
}
$complexMappings[$newId] = [$legacyStringToIntMap[$oldId], (int) $meta];
$intId = $legacyStringToIntMap->stringToLegacy($oldId);
if($intId === null){
//new item without a fixed legacy ID - we can't handle this right now
continue;
}
$complexMappings[$newId] = [$intId, (int) $meta];
}
}
@ -142,10 +146,10 @@ final class ItemTranslator{
}
/**
* @return int[]
* @phpstan-return array{int, int}
* @return int[]|null
* @phpstan-return array{int, int}|null
*/
public function toNetworkId(int $internalId, int $internalMeta) : array{
public function toNetworkIdQuiet(int $internalId, int $internalMeta) : ?array{
if($internalMeta === -1){
$internalMeta = 0x7fff;
}
@ -156,17 +160,27 @@ final class ItemTranslator{
return [$this->simpleCoreToNetMapping[$internalId], $internalMeta];
}
throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta");
return null;
}
/**
* @return int[]
* @phpstan-return array{int, int}
*/
public function toNetworkId(int $internalId, int $internalMeta) : array{
return $this->toNetworkIdQuiet($internalId, $internalMeta) ??
throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta");
}
/**
* @return int[]
* @phpstan-return array{int, int}
* @throws TypeConversionException
*/
public function fromNetworkId(int $networkId, int $networkMeta, ?bool &$isComplexMapping = null) : array{
if(isset($this->complexNetToCoreMapping[$networkId])){
if($networkMeta !== 0){
throw new \UnexpectedValueException("Unexpected non-zero network meta on complex item mapping");
throw new TypeConversionException("Unexpected non-zero network meta on complex item mapping");
}
$isComplexMapping = true;
return $this->complexNetToCoreMapping[$networkId];
@ -175,12 +189,13 @@ final class ItemTranslator{
if(isset($this->simpleNetToCoreMapping[$networkId])){
return [$this->simpleNetToCoreMapping[$networkId], $networkMeta];
}
throw new \UnexpectedValueException("Unmapped network ID/metadata combination $networkId:$networkMeta");
throw new TypeConversionException("Unmapped network ID/metadata combination $networkId:$networkMeta");
}
/**
* @return int[]
* @phpstan-return array{int, int}
* @throws TypeConversionException
*/
public function fromNetworkIdWithWildcardHandling(int $networkId, int $networkMeta) : array{
$isComplexMapping = false;

View File

@ -58,6 +58,7 @@ class TypeConverter{
private const DAMAGE_TAG = "Damage"; //TAG_Int
private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___";
private const PM_ID_TAG = "___Id___";
private const PM_META_TAG = "___Meta___";
/** @var int */
@ -143,26 +144,40 @@ class TypeConverter{
}
$isBlockItem = $itemStack->getId() < 256;
if($itemStack instanceof Durable and $itemStack->getDamage() > 0){
if($nbt !== null){
if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){
$nbt->removeTag(self::DAMAGE_TAG);
$nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing);
}
}else{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
$idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($itemStack->getId(), $itemStack->getMeta());
if($idMeta === null){
//Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with
//other unmapped items.
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId(ItemIds::INFO_UPDATE, 0);
if($nbt === null){
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_ID_TAG, $itemStack->getId());
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
}else{
[$id, $meta] = $idMeta;
if($itemStack instanceof Durable and $itemStack->getDamage() > 0){
if($nbt !== null){
if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){
$nbt->removeTag(self::DAMAGE_TAG);
$nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing);
}
}else{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
if($nbt === null){
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
}
}
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta());
$blockRuntimeId = 0;
if($isBlockItem){
@ -197,14 +212,16 @@ class TypeConverter{
if($compound !== null){
$compound = clone $compound;
if(($idTag = $compound->getTag(self::PM_ID_TAG)) instanceof IntTag){
$id = $idTag->getValue();
$compound->removeTag(self::PM_ID_TAG);
}
if(($damageTag = $compound->getTag(self::DAMAGE_TAG)) instanceof IntTag){
$meta = $damageTag->getValue();
$compound->removeTag(self::DAMAGE_TAG);
if(($conflicted = $compound->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){
$compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
$compound->setTag(self::DAMAGE_TAG, $conflicted);
}elseif($compound->count() === 0){
$compound = null;
}
}elseif(($metaTag = $compound->getTag(self::PM_META_TAG)) instanceof IntTag){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
@ -212,9 +229,9 @@ class TypeConverter{
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
$meta = $metaTag->getValue();
$compound->removeTag(self::PM_META_TAG);
if($compound->count() === 0){
$compound = null;
}
}
if($compound->count() === 0){
$compound = null;
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\player\Player;
class DeathPacketHandler extends PacketHandler{
@ -49,7 +50,7 @@ class DeathPacketHandler extends PacketHandler{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
if($packet->action === PlayerActionPacket::ACTION_RESPAWN){
if($packet->action === PlayerAction::RESPAWN){
$this->player->respawn();
return true;
}

View File

@ -83,6 +83,7 @@ use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\SubClientLoginPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\types\ActorEvent;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
@ -92,6 +93,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
@ -199,14 +201,14 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleActorEvent(ActorEventPacket $packet) : bool{
if($packet->entityRuntimeId !== $this->player->getId()){
if($packet->actorRuntimeId !== $this->player->getId()){
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
return $packet->event === ActorEventPacket::EATING_ITEM;
return $packet->actorRuntimeId === ActorEvent::EATING_ITEM;
}
$this->player->doCloseInventory();
switch($packet->event){
case ActorEventPacket::EATING_ITEM: //TODO: ignore this and handle it server-side
switch($packet->eventId){
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
$item = $this->player->getInventory()->getItemInHand();
if($item->isNull()){
return false;
@ -356,12 +358,12 @@ class InGamePacketHandler extends PacketHandler{
switch($data->getActionType()){
case UseItemTransactionData::ACTION_CLICK_BLOCK:
//TODO: start hack for client spam bug
$clickPos = $data->getClickPos();
$clickPos = $data->getClickPosition();
$spamBug = ($this->lastRightClickData !== null and
microtime(true) - $this->lastRightClickTime < 0.1 and //100ms
$this->lastRightClickData->getPlayerPos()->distanceSquared($data->getPlayerPos()) < 0.00001 and
$this->lastRightClickData->getBlockPos()->equals($data->getBlockPos()) and
$this->lastRightClickData->getClickPos()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
$this->lastRightClickData->getPlayerPosition()->distanceSquared($data->getPlayerPosition()) < 0.00001 and
$this->lastRightClickData->getBlockPosition()->equals($data->getBlockPosition()) and
$this->lastRightClickData->getClickPosition()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
);
//get rid of continued spam if the player clicks and holds right-click
$this->lastRightClickData = $data;
@ -371,9 +373,10 @@ class InGamePacketHandler extends PacketHandler{
}
//TODO: end hack for client spam bug
$blockPos = $data->getBlockPos();
if(!$this->player->interactBlock($blockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($blockPos, $data->getFace());
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($vBlockPos, $data->getFace());
}elseif(
!array_key_exists($windowId = InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID, $this->openHardcodedWindows) &&
$this->player->getCraftingGrid()->getGridWidth() === CraftingGrid::SIZE_BIG
@ -381,7 +384,7 @@ class InGamePacketHandler extends PacketHandler{
//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
$this->openHardcodedWindows[$windowId] = true;
$this->session->sendDataPacket(ContainerOpenPacket::blockInvVec3(
$this->session->sendDataPacket(ContainerOpenPacket::blockInv(
InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID,
WindowTypes::WORKBENCH,
$blockPos
@ -389,9 +392,10 @@ class InGamePacketHandler extends PacketHandler{
}
return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK:
$blockPos = $data->getBlockPos();
if(!$this->player->breakBlock($blockPos)){
$this->onFailedBlockAction($blockPos, null);
$blockPos = $data->getBlockPosition();
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->breakBlock($vBlockPos)){
$this->onFailedBlockAction($vBlockPos, null);
}
return true;
case UseItemTransactionData::ACTION_CLICK_AIR:
@ -432,7 +436,7 @@ class InGamePacketHandler extends PacketHandler{
}
private function handleUseItemOnEntityTransaction(UseItemOnEntityTransactionData $data) : bool{
$target = $this->player->getWorld()->getEntity($data->getEntityRuntimeId());
$target = $this->player->getWorld()->getEntity($data->getActorRuntimeId());
if($target === null){
return false;
}
@ -442,7 +446,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO: use transactiondata for rollbacks here
switch($data->getActionType()){
case UseItemOnEntityTransactionData::ACTION_INTERACT:
if(!$this->player->interactEntity($target, $data->getClickPos())){
if(!$this->player->interactEntity($target, $data->getClickPosition())){
$this->inventoryManager->syncSlot($this->player->getInventory(), $this->player->getInventory()->getHeldItemIndex());
}
return true;
@ -498,7 +502,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO: implement handling for this where it matters
return true;
}
$target = $this->player->getWorld()->getEntity($packet->target);
$target = $this->player->getWorld()->getEntity($packet->targetActorRuntimeId);
if($target === null){
return false;
}
@ -521,7 +525,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
return $this->player->pickBlock(new Vector3($packet->blockX, $packet->blockY, $packet->blockZ), $packet->addUserData);
return $this->player->pickBlock(new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()), $packet->addUserData);
}
public function handleActorPickRequest(ActorPickRequestPacket $packet) : bool{
@ -529,63 +533,63 @@ class InGamePacketHandler extends PacketHandler{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
$pos = new Vector3($packet->x, $packet->y, $packet->z);
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
switch($packet->action){
case PlayerActionPacket::ACTION_START_BREAK:
case PlayerAction::START_BREAK:
if(!$this->player->attackBlock($pos, $packet->face)){
$this->onFailedBlockAction($pos, $packet->face);
}
break;
case PlayerActionPacket::ACTION_ABORT_BREAK:
case PlayerActionPacket::ACTION_STOP_BREAK:
case PlayerAction::ABORT_BREAK:
case PlayerAction::STOP_BREAK:
$this->player->stopBreakBlock($pos);
break;
case PlayerActionPacket::ACTION_START_SLEEPING:
case PlayerAction::START_SLEEPING:
//unused
break;
case PlayerActionPacket::ACTION_STOP_SLEEPING:
case PlayerAction::STOP_SLEEPING:
$this->player->stopSleep();
break;
case PlayerActionPacket::ACTION_JUMP:
case PlayerAction::JUMP:
$this->player->jump();
return true;
case PlayerActionPacket::ACTION_START_SPRINT:
case PlayerAction::START_SPRINT:
if(!$this->player->toggleSprint(true)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_STOP_SPRINT:
case PlayerAction::STOP_SPRINT:
if(!$this->player->toggleSprint(false)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_START_SNEAK:
case PlayerAction::START_SNEAK:
if(!$this->player->toggleSneak(true)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_STOP_SNEAK:
case PlayerAction::STOP_SNEAK:
if(!$this->player->toggleSneak(false)){
$this->player->sendData([$this->player]);
}
return true;
case PlayerActionPacket::ACTION_START_GLIDE:
case PlayerActionPacket::ACTION_STOP_GLIDE:
case PlayerAction::START_GLIDE:
case PlayerAction::STOP_GLIDE:
break; //TODO
case PlayerActionPacket::ACTION_CRACK_BREAK:
case PlayerAction::CRACK_BREAK:
$this->player->continueBreakBlock($pos, $packet->face);
break;
case PlayerActionPacket::ACTION_START_SWIMMING:
case PlayerAction::START_SWIMMING:
break; //TODO
case PlayerActionPacket::ACTION_STOP_SWIMMING:
case PlayerAction::STOP_SWIMMING:
//TODO: handle this when it doesn't spam every damn tick (yet another spam bug!!)
break;
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //TODO: ignored (for now)
case PlayerAction::INTERACT_BLOCK: //TODO: ignored (for now)
break;
case PlayerActionPacket::ACTION_CREATIVE_PLAYER_DESTROY_BLOCK:
case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK:
//TODO: do we need to handle this?
break;
default:
@ -628,7 +632,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
if($packet->entityUniqueId !== $this->player->getId()){
if($packet->targetActorUniqueId !== $this->player->getId()){
return false; //TODO: operators can change other people's permissions using this
}
@ -648,13 +652,13 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
$pos = new Vector3($packet->x, $packet->y, $packet->z);
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
if($pos->distanceSquared($this->player->getLocation()) > 10000){
return false;
}
$block = $this->player->getLocation()->getWorld()->getBlock($pos);
$nbt = $packet->namedtag->getRoot();
$nbt = $packet->nbt->getRoot();
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){
@ -678,7 +682,7 @@ class InGamePacketHandler extends PacketHandler{
return true;
}
$this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->namedtag->getEncodedNbt()));
$this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->nbt->getEncodedNbt()));
}
return false;
@ -712,9 +716,10 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleItemFrameDropItem(ItemFrameDropItemPacket $packet) : bool{
$block = $this->player->getWorld()->getBlockAt($packet->x, $packet->y, $packet->z);
$blockPosition = $packet->blockPosition;
$block = $this->player->getWorld()->getBlockAt($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ());
if($block instanceof ItemFrame and $block->getFramedItem() !== null){
return $this->player->attackBlock(new Vector3($packet->x, $packet->y, $packet->z), $block->getFacing());
return $this->player->attackBlock(new Vector3($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ()), $block->getFacing());
}
return false;
}

View File

@ -31,9 +31,11 @@ use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\BoolGameRule;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\LevelSettings;
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
@ -64,39 +66,46 @@ class PreSpawnPacketHandler extends PacketHandler{
}
public function setUp() : void{
$spawnPosition = $this->player->getSpawn();
$location = $this->player->getLocation();
$pk = new StartGamePacket();
$pk->entityUniqueId = $this->player->getId();
$pk->entityRuntimeId = $this->player->getId();
$pk->playerGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode());
$pk->playerPosition = $this->player->getOffsetPosition($location);
$pk->pitch = $location->pitch;
$pk->yaw = $location->yaw;
$pk->seed = -1;
$pk->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
$pk->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
$pk->difficulty = $location->getWorld()->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX();
$pk->spawnY = $spawnPosition->getFloorY();
$pk->spawnZ = $spawnPosition->getFloorZ();
$pk->hasAchievementsDisabled = true;
$pk->time = $location->getWorld()->getTime();
$pk->eduEditionOffer = 0;
$pk->rainLevel = 0; //TODO: implement these properly
$pk->lightningLevel = 0;
$pk->commandsEnabled = true;
$pk->gameRules = [
$levelSettings = new LevelSettings();
$levelSettings->seed = -1;
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
$levelSettings->difficulty = $location->getWorld()->getDifficulty();
$levelSettings->spawnPosition = BlockPosition::fromVector3($location->getWorld()->getSpawnLocation());
$levelSettings->hasAchievementsDisabled = true;
$levelSettings->time = $location->getWorld()->getTime();
$levelSettings->eduEditionOffer = 0;
$levelSettings->rainLevel = 0; //TODO: implement these properly
$levelSettings->lightningLevel = 0;
$levelSettings->commandsEnabled = true;
$levelSettings->gameRules = [
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
];
$pk->experiments = new Experiments([], false);
$pk->levelId = "";
$pk->worldName = $this->server->getMotd();
$pk->itemTable = GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(); //TODO: check if this is actually needed
$pk->playerMovementSettings = new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false);
$pk->serverSoftwareVersion = sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true));
$this->session->sendDataPacket($pk);
$levelSettings->experiments = new Experiments([], false);
$this->session->sendDataPacket(StartGamePacket::create(
$this->player->getId(),
$this->player->getId(),
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
$this->player->getOffsetPosition($location),
$location->pitch,
$location->yaw,
$levelSettings,
"",
$this->server->getMotd(),
"",
false,
new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false),
0,
0,
"",
false,
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
[],
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries()
));
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());

View File

@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\lang\KnownTranslationKeys;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
@ -34,6 +35,7 @@ use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\resourcepacks\ResourcePackManager;
use function array_map;
@ -113,7 +115,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
self::PACK_CHUNK_SIZE,
(int) ceil($pack->getPackSize() / self::PACK_CHUNK_SIZE),
$pack->getPackSize(),
$pack->getSha256()
$pack->getSha256(),
false,
ResourcePackType::ADDON //TODO: this might be an addon (not behaviour pack), needed to properly support client-side custom items
));
}
$this->session->getLogger()->debug("Player requested download of " . count($packet->packIds) . " resource packs");
@ -130,7 +134,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
//we don't force here, because it doesn't have user-facing effects
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, new Experiments([], false)));
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false)));
$this->session->getLogger()->debug("Applying resource pack stack");
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:

View File

@ -35,7 +35,6 @@ use pocketmine\network\Network;
use pocketmine\network\PacketHandlingException;
use pocketmine\Server;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use raklib\protocol\EncapsulatedPacket;
use raklib\protocol\PacketReliability;
@ -192,10 +191,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$logger->error("Bad packet (error ID $errorId): " . $e->getMessage());
//intentionally doesn't use logException, we don't want spammy packet error traces to appear in release mode
$logger->debug("Origin: " . Filesystem::cleanPath($e->getFile()) . "(" . $e->getLine() . ")");
foreach(Utils::printableTrace($e->getTrace()) as $frame){
$logger->debug($frame);
}
$logger->debug(implode("\n", Utils::printableExceptionInfo($e)));
$session->disconnect("Packet processing error (Error ID: $errorId)");
$this->interface->blockAddress($address, 5);
}

View File

@ -51,65 +51,56 @@ abstract class DefaultPermissions{
self::registerPermission(new Permission(DefaultPermissionNames::BROADCAST_ADMIN, "Allows the user to receive administrative broadcasts"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::BROADCAST_USER, "Allows the user to receive user broadcasts"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ADD, "Allows the user to add a player to the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_REMOVE, "Allows the user to remove a player from the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_RELOAD, "Allows the user to reload the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ENABLE, "Allows the user to enable the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_DISABLE, "Allows the user to disable the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_LIST, "Allows the user to list all players on the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_PLAYER, "Allows the user to ban players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_IP, "Allows the user to ban IP addresses"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_LIST, "Allows the user to list banned players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER, "Allows the user to unban players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_IP, "Allows the user to unban IP addresses"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_PLAYER, "Allows the user to ban players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_OTHER, "Allows the user to clear inventory of other players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_SELF, "Allows the user to clear their own inventory"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DEFAULTGAMEMODE, "Allows the user to change the default gamemode"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DIFFICULTY, "Allows the user to change the game difficulty"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DUMPMEMORY, "Allows the user to dump memory contents"), [$consoleRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_EFFECT, "Allows the user to give/take potion effects"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ENCHANT, "Allows the user to enchant items"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GAMEMODE, "Allows the user to change the gamemode of players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GC, "Allows the user to fire garbage collection tasks"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GIVE, "Allows the user to give items to players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_HELP, "Allows the user to view the help menu"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KICK, "Allows the user to kick players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_OTHER, "Allows the user to kill other players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_SELF, "Allows the user to commit suicide"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_LIST, "Allows the user to list all online players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ME, "Allows the user to perform a chat action"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_OP_GIVE, "Allows the user to give a player operator status"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_OP_TAKE, "Allows the user to take a player's operator status"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_ENABLE, "Allows the user to enable automatic saving"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PARTICLE, "Allows the user to create particle effects"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PLUGINS, "Allows the user to view the list of plugins"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_DISABLE, "Allows the user to disable automatic saving"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_ENABLE, "Allows the user to enable automatic saving"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_PERFORM, "Allows the user to perform a manual save"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAY, "Allows the user to talk as the console"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SEED, "Allows the user to view the seed of the world"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SETWORLDSPAWN, "Allows the user to change the world spawn"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SPAWNPOINT, "Allows the user to change player's spawnpoint"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STATUS, "Allows the user to view the server performance"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STOP, "Allows the user to stop the server"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELEPORT, "Allows the user to teleport players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELL, "Allows the user to privately message another player"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_ADD, "Allows the user to fast-forward time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_QUERY, "Allows the user query the time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_SET, "Allows the user to change the time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_START, "Allows the user to restart the time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_STOP, "Allows the user to stop the time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_QUERY, "Allows the user query the time"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_SELF, "Allows the user to commit suicide"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_OTHER, "Allows the user to kill other players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_SELF, "Allows the user to clear their own inventory"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_OTHER, "Allows the user to clear inventory of other players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ME, "Allows the user to perform a chat action"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELL, "Allows the user to privately message another player"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAY, "Allows the user to talk as the console"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GIVE, "Allows the user to give items to players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_EFFECT, "Allows the user to give/take potion effects"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ENCHANT, "Allows the user to enchant items"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PARTICLE, "Allows the user to create particle effects"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELEPORT, "Allows the user to teleport players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KICK, "Allows the user to kick players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STOP, "Allows the user to stop the server"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_LIST, "Allows the user to list all online players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_HELP, "Allows the user to view the help menu"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PLUGINS, "Allows the user to view the list of plugins"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_VERSION, "Allows the user to view the version of the server"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GAMEMODE, "Allows the user to change the gamemode of players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DEFAULTGAMEMODE, "Allows the user to change the default gamemode"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SEED, "Allows the user to view the seed of the world"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STATUS, "Allows the user to view the server performance"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GC, "Allows the user to fire garbage collection tasks"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DUMPMEMORY, "Allows the user to dump memory contents"), [$consoleRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIMINGS, "Allows the user to records timings for all plugin events"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SPAWNPOINT, "Allows the user to change player's spawnpoint"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SETWORLDSPAWN, "Allows the user to change the world spawn"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TRANSFERSERVER, "Allows the user to transfer self to another server"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIMINGS, "Allows the user to record timings to analyse server performance"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TITLE, "Allows the user to send a title to the specified player"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DIFFICULTY, "Allows the user to change the game difficulty"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TRANSFERSERVER, "Allows the user to transfer self to another server"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_IP, "Allows the user to unban IP addresses"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER, "Allows the user to unban players"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_VERSION, "Allows the user to view the version of the server"), [$everyoneRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ADD, "Allows the user to add a player to the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_DISABLE, "Allows the user to disable the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ENABLE, "Allows the user to enable the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_LIST, "Allows the user to list all players on the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_RELOAD, "Allows the user to reload the server whitelist"), [$operatorRoot]);
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_REMOVE, "Allows the user to remove a player from the server whitelist"), [$operatorRoot]);
}
}

View File

@ -97,6 +97,7 @@ use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
@ -270,9 +271,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$world = $spawnLocation->getWorld();
//load the spawn chunk so we can see the terrain
$world->registerChunkLoader($this->chunkLoader, $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE, true);
$world->registerChunkListener($this, $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE);
$this->usedChunks[World::chunkHash($spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE)] = UsedChunkStatus::NEEDED();
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
$zSpawnChunk = $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE;
$world->registerChunkLoader($this->chunkLoader, $xSpawnChunk, $zSpawnChunk, true);
$world->registerChunkListener($this, $xSpawnChunk, $zSpawnChunk);
$this->usedChunks[World::chunkHash($xSpawnChunk, $zSpawnChunk)] = UsedChunkStatus::NEEDED();
parent::__construct($spawnLocation, $this->playerInfo->getSkin(), $namedtag);
}
@ -2183,7 +2186,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$properties->setGenericFlag(EntityMetadataFlags::ACTION, $this->startAction > -1);
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping ?? new Vector3(0, 0, 0));
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
}
public function sendData(?array $targets, ?array $data = null) : void{

View File

@ -28,6 +28,7 @@ use pocketmine\entity\animation\ArmSwingAnimation;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
use pocketmine\world\particle\BlockPunchParticle;
use pocketmine\world\sound\BlockPunchSound;
use function abs;
@ -70,7 +71,7 @@ final class SurvivalBlockBreakHandler{
if($this->breakSpeed > 0){
$this->player->getWorld()->broadcastPacketToViewers(
$this->blockPos,
LevelEventPacket::create(LevelEventPacket::EVENT_BLOCK_START_BREAK, (int) (65535 * $this->breakSpeed), $this->blockPos)
LevelEventPacket::create(LevelEvent::BLOCK_START_BREAK, (int) (65535 * $this->breakSpeed), $this->blockPos)
);
}
}
@ -147,7 +148,7 @@ final class SurvivalBlockBreakHandler{
if($this->player->getWorld()->isInLoadedTerrain($this->blockPos)){
$this->player->getWorld()->broadcastPacketToViewers(
$this->blockPos,
LevelEventPacket::create(LevelEventPacket::EVENT_BLOCK_STOP_BREAK, 0, $this->blockPos)
LevelEventPacket::create(LevelEvent::BLOCK_STOP_BREAK, 0, $this->blockPos)
);
}
}

View File

@ -30,13 +30,9 @@ use function array_map;
use function array_values;
use function is_array;
use function is_string;
use function phpversion;
use function preg_match;
use function str_replace;
use function stripos;
use function strlen;
use function substr;
use function version_compare;
use function yaml_parse;
class PluginDescription{
@ -247,40 +243,6 @@ class PluginDescription{
return $this->extensions;
}
/**
* Checks if the current PHP runtime has the extensions required by the plugin.
*
* @throws PluginException if there are required extensions missing or have incompatible version, or if the version constraint cannot be parsed
*/
public function checkRequiredExtensions() : void{
foreach($this->extensions as $name => $versionConstrs){
$gotVersion = phpversion($name);
if($gotVersion === false){
throw new PluginException("Required extension $name not loaded");
}
foreach($versionConstrs as $constr){ // versionConstrs_loop
if($constr === "*"){
continue;
}
if($constr === ""){
throw new PluginException("One of the extension version constraints of $name is empty. Consider quoting the version string in plugin.yml");
}
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
// warning: the > character should be quoted in YAML
if(substr($constr, 0, strlen($comparator)) === $comparator){
$version = substr($constr, strlen($comparator));
if(!version_compare($gotVersion, $version, $comparator)){
throw new PluginException("Required extension $name has an incompatible version ($gotVersion not $constr)");
}
continue 2; // versionConstrs_loop
}
}
throw new PluginException("Error parsing version constraint: $constr");
}
}
}
/**
* @return string[]
*/

View File

@ -23,15 +23,11 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Rules\AllOf;
use Respect\Validation\Rules\ArrayType;
use Respect\Validation\Rules\Each;
use Respect\Validation\Rules\In;
use Respect\Validation\Rules\Key;
use Respect\Validation\Rules\StringType;
use Respect\Validation\Validator;
use function array_flip;
use function is_array;
use function is_float;
use function is_int;
use function is_string;
class PluginGraylist{
@ -70,17 +66,27 @@ class PluginGraylist{
* @param mixed[] $array
*/
public static function fromArray(array $array) : PluginGraylist{
$validator = new Validator(
new Key("mode", new In(['whitelist', 'blacklist'], true), true),
new Key("plugins", new AllOf(new ArrayType(), new Each(new StringType())), true)
);
$validator->setName('plugin_list.yml');
try{
$validator->assert($array);
}catch(NestedValidationException $e){
throw new \InvalidArgumentException($e->getFullMessage(), 0, $e);
if(!isset($array["mode"]) || ($array["mode"] !== "whitelist" && $array["mode"] !== "blacklist")){
throw new \InvalidArgumentException("\"mode\" must be set");
}
return new PluginGraylist($array["plugins"], $array["mode"] === 'whitelist');
$isWhitelist = match($array["mode"]){
"whitelist" => true,
"blacklist" => false,
default => throw new \InvalidArgumentException("\"mode\" must be either \"whitelist\" or \"blacklist\"")
};
$plugins = [];
if(isset($array["plugins"])){
if(!is_array($array["plugins"])){
throw new \InvalidArgumentException("\"plugins\" must be an array");
}
foreach($array["plugins"] as $k => $v){
if(!is_string($v) && !is_int($v) && !is_float($v)){
throw new \InvalidArgumentException("\"plugins\" contains invalid element at position $k");
}
$plugins[] = (string) $v;
}
}
return new PluginGraylist($plugins, $isWhitelist);
}
/**

View File

@ -0,0 +1,42 @@
<?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\plugin;
final class PluginLoadTriage{
/**
* @var PluginLoadTriageEntry[]
* @phpstan-var array<string, PluginLoadTriageEntry>
*/
public $plugins = [];
/**
* @var string[][]
* @phpstan-var array<string, list<string>>
*/
public $dependencies = [];
/**
* @var string[][]
* @phpstan-var array<string, list<string>>
*/
public $softDependencies = [];
}

View File

@ -0,0 +1,42 @@
<?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\plugin;
/**
* @internal
*/
final class PluginLoadTriageEntry{
public function __construct(
private string $file,
private PluginLoader $loader,
private PluginDescription $description
){}
public function getFile() : string{ return $this->file; }
public function getLoader() : PluginLoader{ return $this->loader; }
public function getDescription() : PluginDescription{ return $this->description; }
}

View File

@ -0,0 +1,108 @@
<?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\plugin;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use function array_intersect;
use function count;
use function implode;
use function in_array;
use function phpversion;
use function stripos;
use function strlen;
use function substr;
use function version_compare;
final class PluginLoadabilityChecker{
public function __construct(
private string $apiVersion
){}
public function check(PluginDescription $description) : Translatable|null{
$name = $description->getName();
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
return KnownTranslationFactory::pocketmine_plugin_restrictedName();
}
foreach($description->getCompatibleApis() as $api){
if(!VersionString::isValidBaseVersion($api)){
return KnownTranslationFactory::pocketmine_plugin_invalidAPI($api);
}
}
if(!ApiVersion::isCompatible($this->apiVersion, $description->getCompatibleApis())){
return KnownTranslationFactory::pocketmine_plugin_incompatibleAPI(implode(", ", $description->getCompatibleApis()));
}
$ambiguousVersions = ApiVersion::checkAmbiguousVersions($description->getCompatibleApis());
if(count($ambiguousVersions) > 0){
return KnownTranslationFactory::pocketmine_plugin_ambiguousMinAPI(implode(", ", $ambiguousVersions));
}
if(count($description->getCompatibleOperatingSystems()) > 0 and !in_array(Utils::getOS(), $description->getCompatibleOperatingSystems(), true)) {
return KnownTranslationFactory::pocketmine_plugin_incompatibleOS(implode(", ", $description->getCompatibleOperatingSystems()));
}
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
return KnownTranslationFactory::pocketmine_plugin_incompatibleProtocol(implode(", ", $pluginMcpeProtocols));
}
}
foreach($description->getRequiredExtensions() as $extensionName => $versionConstrs){
$gotVersion = phpversion($extensionName);
if($gotVersion === false){
return KnownTranslationFactory::pocketmine_plugin_extensionNotLoaded($extensionName);
}
foreach($versionConstrs as $k => $constr){ // versionConstrs_loop
if($constr === "*"){
continue;
}
if($constr === ""){
return KnownTranslationFactory::pocketmine_plugin_emptyExtensionVersionConstraint(extensionName: $extensionName, constraintIndex: "$k");
}
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
// warning: the > character should be quoted in YAML
if(substr($constr, 0, strlen($comparator)) === $comparator){
$version = substr($constr, strlen($comparator));
if(!version_compare($gotVersion, $version, $comparator)){
return KnownTranslationFactory::pocketmine_plugin_incompatibleExtensionVersion(extensionName: $extensionName, extensionVersion: $gotVersion, pluginRequirement: $constr);
}
continue 2; // versionConstrs_loop
}
}
return KnownTranslationFactory::pocketmine_plugin_invalidExtensionVersionConstraint(extensionName: $extensionName, versionConstraint: $constr);
}
}
return null;
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use pocketmine\event\Cancellable;
use pocketmine\event\Event;
use pocketmine\event\EventPriority;
use pocketmine\event\HandlerListManager;
@ -32,7 +33,6 @@ use pocketmine\event\plugin\PluginDisableEvent;
use pocketmine\event\plugin\PluginEnableEvent;
use pocketmine\event\RegisteredListener;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\permission\DefaultPermissions;
use pocketmine\permission\PermissionManager;
use pocketmine\permission\PermissionParser;
@ -40,9 +40,10 @@ use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use Webmozart\PathUtil\Path;
use function array_intersect;
use function array_diff_key;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function class_exists;
use function count;
@ -50,16 +51,17 @@ use function dirname;
use function file_exists;
use function get_class;
use function implode;
use function in_array;
use function is_a;
use function is_array;
use function is_dir;
use function is_file;
use function is_string;
use function is_subclass_of;
use function iterator_to_array;
use function mkdir;
use function realpath;
use function shuffle;
use function stripos;
use function sprintf;
use function strpos;
use function strtolower;
@ -77,6 +79,8 @@ class PluginManager{
/** @var Plugin[] */
protected $enabledPlugins = [];
private bool $loadPluginsGuard = false;
/**
* @var PluginLoader[]
* @phpstan-var array<class-string<PluginLoader>, PluginLoader>
@ -128,103 +132,85 @@ class PluginManager{
return Path::join(dirname($pluginPath), $pluginName);
}
/**
* @param PluginLoader[] $loaders
*/
public function loadPlugin(string $path, ?array $loaders = null) : ?Plugin{
foreach($loaders ?? $this->fileAssociations as $loader){
if($loader->canLoadPlugin($path)){
$description = $loader->getPluginDescription($path);
if($description instanceof PluginDescription){
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_load($description->getFullName())));
try{
$description->checkRequiredExtensions();
}catch(PluginException $ex){
$this->server->getLogger()->error($ex->getMessage());
return null;
}
private function internalLoadPlugin(string $path, PluginLoader $loader, PluginDescription $description) : ?Plugin{
$language = $this->server->getLanguage();
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_load($description->getFullName())));
$dataFolder = $this->getDataDirectory($path, $description->getName());
if(file_exists($dataFolder) and !is_dir($dataFolder)){
$this->server->getLogger()->error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory");
return null;
}
if(!file_exists($dataFolder)){
mkdir($dataFolder, 0777, true);
}
$dataFolder = $this->getDataDirectory($path, $description->getName());
if(file_exists($dataFolder) and !is_dir($dataFolder)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_badDataFolder($dataFolder)
)));
return null;
}
if(!file_exists($dataFolder)){
mkdir($dataFolder, 0777, true);
}
$prefixed = $loader->getAccessProtocol() . $path;
$loader->loadPlugin($prefixed);
$prefixed = $loader->getAccessProtocol() . $path;
$loader->loadPlugin($prefixed);
$mainClass = $description->getMain();
if(!class_exists($mainClass, true)){
$this->server->getLogger()->error("Main class for plugin " . $description->getName() . " not found");
return null;
}
if(!is_a($mainClass, Plugin::class, true)){
$this->server->getLogger()->error("Main class for plugin " . $description->getName() . " is not an instance of " . Plugin::class);
return null;
}
$mainClass = $description->getMain();
if(!class_exists($mainClass, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassNotFound()
)));
return null;
}
if(!is_a($mainClass, Plugin::class, true)){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_mainClassWrongType(Plugin::class)
)));
return null;
}
$permManager = PermissionManager::getInstance();
$opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){
foreach($perms as $perm){
$permManager->addPermission($perm);
switch($default){
case PermissionParser::DEFAULT_TRUE:
$everyoneRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_OP:
$opRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_NOT_OP:
//TODO: I don't think anyone uses this, and it currently relies on some magic inside PermissibleBase
//to ensure that the operator override actually applies.
//Explore getting rid of this.
//The following grants this permission to anyone who has the "everyone" root permission.
//However, if the operator root node (which has higher priority) is present, the
//permission will be denied instead.
$everyoneRoot->addChild($perm->getName(), true);
$opRoot->addChild($perm->getName(), false);
break;
default:
break;
}
}
}
/**
* @var Plugin $plugin
* @see Plugin::__construct()
*/
$plugin = new $mainClass($loader, $this->server, $description, $dataFolder, $prefixed, new DiskResourceProvider($prefixed . "/resources/"));
$this->plugins[$plugin->getDescription()->getName()] = $plugin;
return $plugin;
$permManager = PermissionManager::getInstance();
$opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){
foreach($perms as $perm){
$permManager->addPermission($perm);
switch($default){
case PermissionParser::DEFAULT_TRUE:
$everyoneRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_OP:
$opRoot->addChild($perm->getName(), true);
break;
case PermissionParser::DEFAULT_NOT_OP:
//TODO: I don't think anyone uses this, and it currently relies on some magic inside PermissibleBase
//to ensure that the operator override actually applies.
//Explore getting rid of this.
//The following grants this permission to anyone who has the "everyone" root permission.
//However, if the operator root node (which has higher priority) is present, the
//permission will be denied instead.
$everyoneRoot->addChild($perm->getName(), true);
$opRoot->addChild($perm->getName(), false);
break;
default:
break;
}
}
}
return null;
/**
* @var Plugin $plugin
* @see Plugin::__construct()
*/
$plugin = new $mainClass($loader, $this->server, $description, $dataFolder, $prefixed, new DiskResourceProvider($prefixed . "/resources/"));
$this->plugins[$plugin->getDescription()->getName()] = $plugin;
return $plugin;
}
/**
* @param string[]|null $newLoaders
* @phpstan-param list<class-string<PluginLoader>> $newLoaders
*
* @return Plugin[]
*/
public function loadPlugins(string $directory, ?array $newLoaders = null) : array{
if(!is_dir($directory)){
return [];
}
$plugins = [];
$loadedPlugins = [];
$dependencies = [];
$softDependencies = [];
private function triagePlugins(string $path, PluginLoadTriage $triage, ?array $newLoaders = null) : void{
if(is_array($newLoaders)){
$loaders = [];
foreach($newLoaders as $key){
@ -236,8 +222,18 @@ class PluginManager{
$loaders = $this->fileAssociations;
}
$files = iterator_to_array(new \FilesystemIterator($directory, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
shuffle($files); //this prevents plugins implicitly relying on the filesystem name order when they should be using dependency properties
if(is_dir($path)){
$files = iterator_to_array(new \FilesystemIterator($path, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
shuffle($files); //this prevents plugins implicitly relying on the filesystem name order when they should be using dependency properties
}elseif(is_file($path)){
$realPath = realpath($path);
if($realPath === false) throw new AssumptionFailedError("realpath() should not return false on an accessible, existing file");
$files = [$realPath];
}else{
return;
}
$loadabilityChecker = new PluginLoadabilityChecker($this->server->getApiVersion());
foreach($loaders as $loader){
foreach($files as $file){
if(!is_string($file)) throw new AssumptionFailedError("FilesystemIterator current should be string when using CURRENT_AS_PATHNAME");
@ -262,147 +258,160 @@ class PluginManager{
}
$name = $description->getName();
if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, KnownTranslationFactory::pocketmine_plugin_restrictedName())));
if(($loadabilityError = $loadabilityChecker->check($description)) !== null){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, $loadabilityError)));
continue;
}
if(strpos($name, " ") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_spacesDiscouraged($name)));
}
if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
if(isset($triage->plugins[$name]) or $this->getPlugin($name) instanceof Plugin){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_duplicateError($name)));
continue;
}
foreach($description->getCompatibleApis() as $api){
if(!VersionString::isValidBaseVersion($api)){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_invalidAPI($api)
)));
continue 2;
}
}
if(!ApiVersion::isCompatible($this->server->getApiVersion(), $description->getCompatibleApis())){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_incompatibleAPI(implode(", ", $description->getCompatibleApis()))
)));
continue;
}
$ambiguousVersions = ApiVersion::checkAmbiguousVersions($description->getCompatibleApis());
if(count($ambiguousVersions) > 0){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_ambiguousMinAPI(implode(", ", $ambiguousVersions))
)));
continue;
}
if(count($description->getCompatibleOperatingSystems()) > 0 and !in_array(Utils::getOS(), $description->getCompatibleOperatingSystems(), true)) {
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_incompatibleOS(implode(", ", $description->getCompatibleOperatingSystems()))
)));
continue;
}
if(count($pluginMcpeProtocols = $description->getCompatibleMcpeProtocols()) > 0){
$serverMcpeProtocols = [ProtocolInfo::CURRENT_PROTOCOL];
if(count(array_intersect($pluginMcpeProtocols, $serverMcpeProtocols)) === 0){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_incompatibleProtocol(implode(", ", $pluginMcpeProtocols))
)));
continue;
}
if(strpos($name, " ") !== false){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_spacesDiscouraged($name)));
}
if($this->graylist !== null and !$this->graylist->isAllowed($name)){
$this->server->getLogger()->notice($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
"Disallowed by graylist"
$this->graylist->isWhitelist() ? KnownTranslationFactory::pocketmine_plugin_disallowedByWhitelist() : KnownTranslationFactory::pocketmine_plugin_disallowedByBlacklist()
)));
continue;
}
$plugins[$name] = $file;
$softDependencies[$name] = array_merge($softDependencies[$name] ?? [], $description->getSoftDepend());
$dependencies[$name] = $description->getDepend();
$triage->plugins[$name] = new PluginLoadTriageEntry($file, $loader, $description);
$triage->softDependencies[$name] = array_merge($triage->softDependencies[$name] ?? [], $description->getSoftDepend());
$triage->dependencies[$name] = $description->getDepend();
foreach($description->getLoadBefore() as $before){
if(isset($softDependencies[$before])){
$softDependencies[$before][] = $name;
if(isset($triage->softDependencies[$before])){
$triage->softDependencies[$before][] = $name;
}else{
$softDependencies[$before] = [$name];
$triage->softDependencies[$before] = [$name];
}
}
}
}
}
while(count($plugins) > 0){
/**
* @param string[][] $dependencyLists
* @param Plugin[] $loadedPlugins
*/
private function checkDepsForTriage(string $pluginName, string $dependencyType, array &$dependencyLists, array $loadedPlugins, PluginLoadTriage $triage) : void{
if(isset($dependencyLists[$pluginName])){
foreach($dependencyLists[$pluginName] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
$this->server->getLogger()->debug("Successfully resolved $dependencyType dependency \"$dependency\" for plugin \"$pluginName\"");
unset($dependencyLists[$pluginName][$key]);
}elseif(array_key_exists($dependency, $triage->plugins)){
$this->server->getLogger()->debug("Deferring resolution of $dependencyType dependency \"$dependency\" for plugin \"$pluginName\" (found but not loaded yet)");
}
}
if(count($dependencyLists[$pluginName]) === 0){
unset($dependencyLists[$pluginName]);
}
}
}
/**
* @return Plugin[]
*/
public function loadPlugins(string $path) : array{
if($this->loadPluginsGuard){
throw new \LogicException(__METHOD__ . "() cannot be called from within itself");
}
$this->loadPluginsGuard = true;
$triage = new PluginLoadTriage();
$this->triagePlugins($path, $triage);
$loadedPlugins = [];
while(count($triage->plugins) > 0){
$loadedThisLoop = 0;
foreach($plugins as $name => $file){
if(isset($dependencies[$name])){
foreach($dependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
unset($dependencies[$name][$key]);
}elseif(!isset($plugins[$dependency])){
$this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_unknownDependency($dependency)
)));
unset($plugins[$name]);
continue 2;
}
}
foreach($triage->plugins as $name => $entry){
$this->checkDepsForTriage($name, "hard", $triage->dependencies, $loadedPlugins, $triage);
$this->checkDepsForTriage($name, "soft", $triage->softDependencies, $loadedPlugins, $triage);
if(count($dependencies[$name]) === 0){
unset($dependencies[$name]);
}
}
if(isset($softDependencies[$name])){
foreach($softDependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
$this->server->getLogger()->debug("Successfully resolved soft dependency \"$dependency\" for plugin \"$name\"");
unset($softDependencies[$name][$key]);
}elseif(!isset($plugins[$dependency])){
//this dependency is never going to be resolved, so don't bother trying
$this->server->getLogger()->debug("Skipping resolution of missing soft dependency \"$dependency\" for plugin \"$name\"");
unset($softDependencies[$name][$key]);
}else{
$this->server->getLogger()->debug("Deferring resolution of soft dependency \"$dependency\" for plugin \"$name\" (found but not loaded yet)");
}
}
if(count($softDependencies[$name]) === 0){
unset($softDependencies[$name]);
}
}
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
unset($plugins[$name]);
if(!isset($triage->dependencies[$name]) and !isset($triage->softDependencies[$name])){
unset($triage->plugins[$name]);
$loadedThisLoop++;
if(($plugin = $this->loadPlugin($file, $loaders)) instanceof Plugin){
$oldRegisteredLoaders = $this->fileAssociations;
if(($plugin = $this->internalLoadPlugin($entry->getFile(), $entry->getLoader(), $entry->getDescription())) instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}else{
$this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_genericLoadError($name)));
$diffLoaders = [];
foreach($this->fileAssociations as $k => $loader){
if(!array_key_exists($k, $oldRegisteredLoaders)){
$diffLoaders[] = $k;
}
}
if(count($diffLoaders) !== 0){
$this->server->getLogger()->debug("Plugin $name registered a new plugin loader during load, scanning for new plugins");
$plugins = $triage->plugins;
$this->triagePlugins($path, $triage, $diffLoaders);
$diffPlugins = array_diff_key($triage->plugins, $plugins);
$this->server->getLogger()->debug("Re-triage found plugins: " . implode(", ", array_keys($diffPlugins)));
}
}
}
}
if($loadedThisLoop === 0){
//No plugins loaded :(
foreach($plugins as $name => $file){
//check for skippable soft dependencies first, in case the dependents could resolve hard dependencies
foreach($triage->plugins as $name => $file){
if(isset($triage->softDependencies[$name]) && !isset($triage->dependencies[$name])){
foreach($triage->softDependencies[$name] as $k => $dependency){
if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){
$this->server->getLogger()->debug("Skipping resolution of missing soft dependency \"$dependency\" for plugin \"$name\"");
unset($triage->softDependencies[$name][$k]);
}
}
if(count($triage->softDependencies[$name]) === 0){
unset($triage->softDependencies[$name]);
continue 2; //go back to the top and try again
}
}
}
foreach($triage->plugins as $name => $file){
if(isset($triage->dependencies[$name])){
$unknownDependencies = [];
foreach($triage->dependencies[$name] as $k => $dependency){
if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){
//assume that the plugin is never going to be loaded
//by this point all soft dependencies have been ignored if they were able to be, so
//there's no chance of this dependency ever being resolved
$unknownDependencies[$dependency] = $dependency;
}
}
if(count($unknownDependencies) > 0){
$this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$name,
KnownTranslationFactory::pocketmine_plugin_unknownDependency(implode(", ", $unknownDependencies))
)));
unset($triage->plugins[$name]);
}
}
}
foreach($triage->plugins as $name => $file){
$this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, KnownTranslationFactory::pocketmine_plugin_circularDependency())));
}
$plugins = [];
break;
}
}
$this->loadPluginsGuard = false;
return $loadedPlugins;
}
@ -523,6 +532,14 @@ class PluginManager{
$handleCancelled = false;
if(isset($tags[ListenerMethodTags::HANDLE_CANCELLED])){
if(!is_a($eventClass, Cancellable::class, true)){
throw new PluginException(sprintf(
"Event handler %s() declares @%s for non-cancellable event of type %s",
Utils::getNiceClosureName($handlerClosure),
ListenerMethodTags::HANDLE_CANCELLED,
$eventClass
));
}
switch(strtolower($tags[ListenerMethodTags::HANDLE_CANCELLED])){
case "true":
case "":

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use DaveRandom\CallbackValidator\CallbackType;
use DaveRandom\CallbackValidator\ReturnType;
use pocketmine\utils\Utils;
/**
@ -49,7 +51,7 @@ class ClosureTask extends Task{
* @phpstan-param \Closure() : void $closure
*/
public function __construct(\Closure $closure){
Utils::validateCallableSignature(function() : void{}, $closure);
Utils::validateCallableSignature(new CallbackType(new ReturnType()), $closure);
$this->closure = $closure;
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\utils;
use Webmozart\PathUtil\Path;
use function array_change_key_case;
use function array_fill_keys;
use function array_keys;
use function array_shift;
use function count;
@ -170,7 +171,7 @@ class Config{
$config = null;
switch($this->type){
case Config::PROPERTIES:
$config = $this->parseProperties($content);
$config = self::parseProperties($content);
break;
case Config::JSON:
$config = json_decode($content, true);
@ -183,7 +184,7 @@ class Config{
$config = unserialize($content);
break;
case Config::ENUM:
$config = self::parseList($content);
$config = array_fill_keys(self::parseList($content), true);
break;
default:
throw new \InvalidStateException("Config type is unknown");
@ -211,7 +212,7 @@ class Config{
$content = null;
switch($this->type){
case Config::PROPERTIES:
$content = $this->writeProperties();
$content = self::writeProperties($this->config);
break;
case Config::JSON:
$content = json_encode($this->config, $this->jsonOptions);
@ -223,7 +224,7 @@ class Config{
$content = serialize($this->config);
break;
case Config::ENUM:
$content = implode("\r\n", array_keys($this->config));
$content = self::writeList(array_keys($this->config));
break;
default:
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
@ -509,28 +510,38 @@ class Config{
}
/**
* @return true[]
* @phpstan-return array<string, true>
* @return string[]
* @phpstan-return list<string>
*/
private static function parseList(string $content) : array{
public static function parseList(string $content) : array{
$result = [];
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
$v = trim($v);
if($v == ""){
if($v === ""){
continue;
}
$result[$v] = true;
$result[] = $v;
}
return $result;
}
private function writeProperties() : string{
/**
* @param string[] $entries
* @phpstan-param list<string> $entries
*/
public static function writeList(array $entries) : string{
return implode("\n", $entries);
}
/**
* @param string[]|int[]|float[]|bool[] $config
* @phpstan-param array<string, string|int|float|bool> $config
*/
public static function writeProperties(array $config) : string{
$content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n";
foreach($this->config as $k => $v){
foreach($config as $k => $v){
if(is_bool($v)){
$v = $v ? "on" : "off";
}elseif(is_array($v)){
$v = implode(";", $v);
}
$content .= $k . "=" . $v . "\r\n";
}
@ -539,9 +550,10 @@ class Config{
}
/**
* @return mixed[]
* @return string[]|int[]|float[]|bool[]
* @phpstan-return array<string, string|int|float|bool>
*/
private function parseProperties(string $content) : array{
public static function parseProperties(string $content) : array{
$result = [];
if(preg_match_all('/^\s*([a-zA-Z0-9\-_\.]+)[ \t]*=([^\r\n]*)/um', $content, $matches) > 0){ //false or 0 matches
foreach($matches[1] as $i => $k){
@ -565,10 +577,7 @@ class Config{
};
break;
}
if(isset($result[$k])){
\GlobalLogger::get()->debug("[Config] Repeated property " . $k . " on file " . $this->file);
}
$result[$k] = $v;
$result[(string) $k] = $v;
}
}

View File

@ -32,6 +32,7 @@ use function curl_getinfo;
use function curl_init;
use function curl_setopt_array;
use function explode;
use function is_int;
use function is_string;
use function preg_match;
use function socket_close;
@ -218,8 +219,8 @@ class Internet{
throw new InternetException(curl_error($ch));
}
if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set");
/** @var int $httpCode */
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if(!is_int($httpCode)) throw new AssumptionFailedError("curl_getinfo(CURLINFO_HTTP_CODE) always returns int");
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);

View File

@ -24,14 +24,10 @@ declare(strict_types=1);
namespace pocketmine\utils;
use LogLevel;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\thread\Thread;
use pocketmine\thread\Worker;
use function get_class;
use function is_int;
use function preg_replace;
use function implode;
use function sprintf;
use function trim;
use const PHP_EOL;
use const PTHREADS_INHERIT_NONE;
@ -136,44 +132,11 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
* @return void
*/
public function logException(\Throwable $e, $trace = null){
if($trace === null){
$trace = $e->getTrace();
}
$this->buffer(function() use ($e, $trace) : void{
$this->critical(self::printExceptionMessage($e));
foreach(Utils::printableTrace($trace) as $line){
$this->critical($line);
}
for($prev = $e->getPrevious(); $prev !== null; $prev = $prev->getPrevious()){
$this->critical("Previous: " . self::printExceptionMessage($prev));
foreach(Utils::printableTrace($prev->getTrace()) as $line){
$this->critical(" " . $line);
}
}
});
$this->critical(implode("\n", Utils::printableExceptionInfo($e, $trace)));
$this->syncFlushBuffer();
}
private static function printExceptionMessage(\Throwable $e) : string{
$errstr = preg_replace('/\s+/', ' ', trim($e->getMessage()));
$errno = $e->getCode();
if(is_int($errno)){
try{
$errno = ErrorTypeToStringMap::get($errno);
}catch(\InvalidArgumentException $ex){
//pass
}
}
$errfile = Filesystem::cleanPath($e->getFile());
$errline = $e->getLine();
return get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
}
public function log($level, $message){
switch($level){
case LogLevel::EMERGENCY:
@ -244,12 +207,11 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
$this->synchronized(function() use ($message, $level, $time) : void{
Terminal::writeLine($message);
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
foreach($this->attachments as $attachment){
$attachment->call($level, $message);
}
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
});
}

View File

@ -0,0 +1,80 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function function_exists;
use function pcntl_async_signals;
use function pcntl_signal;
use function sapi_windows_set_ctrl_handler;
use const PHP_WINDOWS_EVENT_CTRL_BREAK;
use const PHP_WINDOWS_EVENT_CTRL_C;
use const SIG_DFL;
use const SIGHUP;
use const SIGINT;
use const SIGTERM;
final class SignalHandler{
/** @phpstan-var (\Closure(int) : void)|null */
private ?\Closure $interruptCallback;
/**
* @phpstan-param \Closure() : void $interruptCallback
*/
public function __construct(\Closure $interruptCallback){
$this->interruptCallback = $interruptCallback;
if(function_exists('sapi_windows_set_ctrl_handler')){
sapi_windows_set_ctrl_handler($this->interruptCallback = function(int $signo) use ($interruptCallback) : void{
if($signo === PHP_WINDOWS_EVENT_CTRL_C || $signo === PHP_WINDOWS_EVENT_CTRL_BREAK){
$interruptCallback();
}
});
}elseif(function_exists('pcntl_signal')){
foreach([
SIGTERM,
SIGINT,
SIGHUP
] as $signal){
pcntl_signal($signal, $this->interruptCallback = fn(int $signo) => $interruptCallback());
}
pcntl_async_signals(true);
}else{
//no supported signal handlers :(
}
}
public function unregister() : void{
if(function_exists('sapi_windows_set_ctrl_handler')){
sapi_windows_set_ctrl_handler($this->interruptCallback, false);
}elseif(function_exists('pcntl_signal')){
foreach([
SIGTERM,
SIGINT,
SIGHUP
] as $signal){
pcntl_signal($signal, SIG_DFL);
}
}
}
}

View File

@ -28,6 +28,7 @@ declare(strict_types=1);
namespace pocketmine\utils;
use DaveRandom\CallbackValidator\CallbackType;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use function array_combine;
@ -46,13 +47,17 @@ use function file;
use function file_exists;
use function file_get_contents;
use function function_exists;
use function get_class;
use function get_current_user;
use function get_loaded_extensions;
use function getenv;
use function gettype;
use function implode;
use function interface_exists;
use function is_a;
use function is_array;
use function is_bool;
use function is_int;
use function is_object;
use function is_string;
use function mb_check_encoding;
@ -384,6 +389,49 @@ final class Utils{
return -1;
}
private static function printableExceptionMessage(\Throwable $e) : string{
$errstr = preg_replace('/\s+/', ' ', trim($e->getMessage()));
$errno = $e->getCode();
if(is_int($errno)){
try{
$errno = ErrorTypeToStringMap::get($errno);
}catch(\InvalidArgumentException $ex){
//pass
}
}
$errfile = Filesystem::cleanPath($e->getFile());
$errline = $e->getLine();
return get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
}
/**
* @param mixed[] $trace
* @return string[]
*/
public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{
if($trace === null){
$trace = $e->getTrace();
}
$lines = [self::printableExceptionMessage($e)];
$lines[] = "--- Stack trace ---";
foreach(Utils::printableTrace($trace) as $line){
$lines[] = " " . $line;
}
for($prev = $e->getPrevious(); $prev !== null; $prev = $prev->getPrevious()){
$lines[] = "--- Previous ---";
$lines[] = self::printableExceptionMessage($prev);
foreach(Utils::printableTrace($prev->getTrace()) as $line){
$lines[] = " " . $line;
}
}
$lines[] = "--- End of exception information ---";
return $lines;
}
/**
* @param mixed[][] $trace
* @phpstan-param list<array<string, mixed>> $trace
@ -467,18 +515,20 @@ final class Utils{
* @phpstan-param class-string $baseName
*/
public static function testValidInstance(string $className, string $baseName) : void{
$baseInterface = false;
if(!class_exists($baseName)){
throw new \InvalidArgumentException("Base class $baseName does not exist");
if(!interface_exists($baseName)){
throw new \InvalidArgumentException("Base class $baseName does not exist");
}
$baseInterface = true;
}
if(!class_exists($className)){
throw new \InvalidArgumentException("Class $className does not exist");
throw new \InvalidArgumentException("Class $className does not exist or is not a class");
}
if(!is_a($className, $baseName, true)){
throw new \InvalidArgumentException("Class $className does not " . ($baseInterface ? "implement" : "extend") . " $baseName");
}
$base = new \ReflectionClass($baseName);
$class = new \ReflectionClass($className);
if(!$class->isSubclassOf($baseName)){
throw new \InvalidArgumentException("Class $className does not " . ($base->isInterface() ? "implement" : "extend") . " " . $baseName);
}
if(!$class->isInstantiable()){
throw new \InvalidArgumentException("Class $className cannot be constructed");
}
@ -488,17 +538,20 @@ final class Utils{
* Verifies that the given callable is compatible with the desired signature. Throws a TypeError if they are
* incompatible.
*
* @param callable $signature Dummy callable with the required parameters and return type
* @param callable $subject Callable to check the signature of
* @phpstan-param anyCallable $signature
* @phpstan-param anyCallable $subject
* @param callable|CallbackType $signature Dummy callable with the required parameters and return type
* @param callable $subject Callable to check the signature of
* @phpstan-param anyCallable|CallbackType $signature
* @phpstan-param anyCallable $subject
*
* @throws \DaveRandom\CallbackValidator\InvalidCallbackException
* @throws \TypeError
*/
public static function validateCallableSignature(callable $signature, callable $subject) : void{
if(!($sigType = CallbackType::createFromCallable($signature))->isSatisfiedBy($subject)){
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($subject) . "` must be compatible with `" . $sigType . "`");
public static function validateCallableSignature(callable|CallbackType $signature, callable $subject) : void{
if(!($signature instanceof CallbackType)){
$signature = CallbackType::createFromCallable($signature);
}
if(!$signature->isSatisfiedBy($subject)){
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($subject) . "` must be compatible with `" . $signature . "`");
}
}

View File

@ -63,6 +63,7 @@ use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\player\Player;
use pocketmine\scheduler\AsyncPool;
@ -411,8 +412,10 @@ class World implements ChunkManager{
$this->maxY = $this->provider->getWorldMaxY();
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_preparing($this->displayName)));
$this->generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator(), true);
//TODO: validate generator options
$generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ??
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
$generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions());
$this->generator = $generator->getGeneratorClass();
$this->chunkPopulationRequestQueue = new \SplQueue();
$this->addOnUnloadCallback(function() : void{
$this->logger->debug("Cancelling unfulfilled generation requests");
@ -950,11 +953,17 @@ class World implements ChunkManager{
}
$fullBlock = $this->getBlockAt($b->x, $b->y, $b->z);
$packets[] = UpdateBlockPacket::create($b->x, $b->y, $b->z, RuntimeBlockMapping::getInstance()->toRuntimeId($fullBlock->getFullId()));
$blockPosition = BlockPosition::fromVector3($b);
$packets[] = UpdateBlockPacket::create(
$blockPosition,
RuntimeBlockMapping::getInstance()->toRuntimeId($fullBlock->getFullId()),
UpdateBlockPacket::FLAG_NETWORK,
UpdateBlockPacket::DATA_LAYER_NORMAL
);
$tile = $this->getTileAt($b->x, $b->y, $b->z);
if($tile instanceof Spawnable){
$packets[] = BlockActorDataPacket::create($b->x, $b->y, $b->z, $tile->getSerializedSpawnCompound());
$packets[] = BlockActorDataPacket::create($blockPosition, $tile->getSerializedSpawnCompound());
}
}
@ -1601,7 +1610,7 @@ class World implements ChunkManager{
$block->writeStateToWorld();
$pos = $block->getPosition();
$chunkHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
@ -1611,7 +1620,7 @@ class World implements ChunkManager{
}
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $pos;
foreach($this->getChunkListeners($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE) as $listener){
foreach($this->getChunkListeners($chunkX, $chunkZ) as $listener){
$listener->onBlockChanged($pos);
}
@ -2082,7 +2091,7 @@ class World implements ChunkManager{
/**
* Returns the chunks adjacent to the specified chunk.
*
* @return (Chunk|null)[]
* @return Chunk[]|null[]
*/
public function getAdjacentChunks(int $x, int $z) : array{
$result = [];
@ -2115,66 +2124,6 @@ class World implements ChunkManager{
return isset($this->chunkLock[World::chunkHash($chunkX, $chunkZ)]);
}
private function drainPopulationRequestQueue() : void{
$failed = [];
while(count($this->activeChunkPopulationTasks) < $this->maxConcurrentChunkPopulationTasks && !$this->chunkPopulationRequestQueue->isEmpty()){
$nextChunkHash = $this->chunkPopulationRequestQueue->dequeue();
World::getXZ($nextChunkHash, $nextChunkX, $nextChunkZ);
if(isset($this->chunkPopulationRequestMap[$nextChunkHash])){
assert(!isset($this->activeChunkPopulationTasks[$nextChunkHash]), "Population for chunk $nextChunkX $nextChunkZ already running");
$this->orderChunkPopulation($nextChunkX, $nextChunkZ, null);
if(!isset($this->activeChunkPopulationTasks[$nextChunkHash])){
$failed[] = $nextChunkHash;
}
}
}
//these requests failed even though they weren't rate limited; we can't directly re-add them to the back of the
//queue because it would result in an infinite loop
foreach($failed as $hash){
$this->chunkPopulationRequestQueue->enqueue($hash);
}
}
public function generateChunkCallback(int $x, int $z, ?Chunk $chunk) : void{
Timings::$generationCallback->startTiming();
if(isset($this->chunkPopulationRequestMap[$index = World::chunkHash($x, $z)]) && isset($this->activeChunkPopulationTasks[$index])){
if($chunk === null){
throw new AssumptionFailedError("Primary chunk should never be NULL");
}
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->unlockChunk($x + $xx, $z + $zz);
}
}
$oldChunk = $this->loadChunk($x, $z);
$this->setChunk($x, $z, $chunk, false);
if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){
(new ChunkPopulateEvent($this, $x, $z, $chunk))->call();
foreach($this->getChunkListeners($x, $z) as $listener){
$listener->onChunkPopulated($x, $z, $chunk);
}
}
unset($this->activeChunkPopulationTasks[$index]);
$promise = $this->chunkPopulationRequestMap[$index];
unset($this->chunkPopulationRequestMap[$index]);
$promise->resolve($chunk);
$this->drainPopulationRequestQueue();
}elseif($this->isChunkLocked($x, $z)){
$this->unlockChunk($x, $z);
if($chunk !== null){
$this->setChunk($x, $z, $chunk, false);
}
$this->drainPopulationRequestQueue();
}elseif($chunk !== null){
$this->setChunk($x, $z, $chunk, false);
}
Timings::$generationCallback->stopTiming();
}
/**
* @param bool $deleteEntitiesAndTiles Whether to delete entities and tiles on the old chunk, or transfer them to the new one
*/
@ -2767,6 +2716,27 @@ class World implements ChunkManager{
return $promise;
}
private function drainPopulationRequestQueue() : void{
$failed = [];
while(count($this->activeChunkPopulationTasks) < $this->maxConcurrentChunkPopulationTasks && !$this->chunkPopulationRequestQueue->isEmpty()){
$nextChunkHash = $this->chunkPopulationRequestQueue->dequeue();
World::getXZ($nextChunkHash, $nextChunkX, $nextChunkZ);
if(isset($this->chunkPopulationRequestMap[$nextChunkHash])){
assert(!isset($this->activeChunkPopulationTasks[$nextChunkHash]), "Population for chunk $nextChunkX $nextChunkZ already running");
$this->orderChunkPopulation($nextChunkX, $nextChunkZ, null);
if(!isset($this->activeChunkPopulationTasks[$nextChunkHash])){
$failed[] = $nextChunkHash;
}
}
}
//these requests failed even though they weren't rate limited; we can't directly re-add them to the back of the
//queue because it would result in an infinite loop
foreach($failed as $hash){
$this->chunkPopulationRequestQueue->enqueue($hash);
}
}
/**
* Attempts to initiate asynchronous generation/population of the target chunk, if it's currently reasonable to do
* so (and if it isn't already generated/populated).
@ -2851,6 +2821,44 @@ class World implements ChunkManager{
return $result;
}
/**
* @param Chunk[] $adjacentChunks
* @phpstan-param array<int, Chunk> $adjacentChunks
*/
public function generateChunkCallback(int $x, int $z, Chunk $chunk, array $adjacentChunks) : void{
Timings::$generationCallback->startTiming();
if(isset($this->chunkPopulationRequestMap[$index = World::chunkHash($x, $z)]) && isset($this->activeChunkPopulationTasks[$index])){
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->unlockChunk($x + $xx, $z + $zz);
}
}
$oldChunk = $this->loadChunk($x, $z);
$this->setChunk($x, $z, $chunk, false);
foreach($adjacentChunks as $adjacentChunkHash => $adjacentChunk){
World::getXZ($adjacentChunkHash, $xAdjacentChunk, $zAdjacentChunk);
$this->setChunk($xAdjacentChunk, $zAdjacentChunk, $adjacentChunk);
}
if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){
(new ChunkPopulateEvent($this, $x, $z, $chunk))->call();
foreach($this->getChunkListeners($x, $z) as $listener){
$listener->onChunkPopulated($x, $z, $chunk);
}
}
unset($this->activeChunkPopulationTasks[$index]);
$promise = $this->chunkPopulationRequestMap[$index];
unset($this->chunkPopulationRequestMap[$index]);
$promise->resolve($chunk);
$this->drainPopulationRequestQueue();
}
Timings::$generationCallback->stopTiming();
}
public function doChunkGarbageCollection() : void{
$this->timings->doChunkGC->startTiming();

View File

@ -38,6 +38,7 @@ use pocketmine\world\format\io\FormatConverter;
use pocketmine\world\format\io\WorldProviderManager;
use pocketmine\world\format\io\WritableWorldProvider;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\InvalidGeneratorOptionsException;
use Webmozart\PathUtil\Path;
use function array_keys;
use function array_shift;
@ -220,15 +221,28 @@ class WorldManager{
)));
return false;
}
try{
GeneratorManager::getInstance()->getGenerator($provider->getWorldData()->getGenerator(), true);
}catch(\InvalidArgumentException $e){
$generatorEntry = GeneratorManager::getInstance()->getGenerator($provider->getWorldData()->getGenerator());
if($generatorEntry === null){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_loadError(
$name,
KnownTranslationFactory::pocketmine_level_unknownGenerator($provider->getWorldData()->getGenerator())
)));
return false;
}
try{
$generatorEntry->validateGeneratorOptions($provider->getWorldData()->getGeneratorOptions());
}catch(InvalidGeneratorOptionsException $e){
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_loadError(
$name,
KnownTranslationFactory::pocketmine_level_invalidGeneratorOptions(
$provider->getWorldData()->getGeneratorOptions(),
$provider->getWorldData()->getGenerator(),
$e->getMessage()
)
)));
return false;
}
if(!($provider instanceof WritableWorldProvider)){
if(!$autoUpgrade){
throw new UnsupportedWorldFormatException("World \"$name\" is in an unsupported format and needs to be upgraded");

View File

@ -29,8 +29,6 @@ namespace pocketmine\world\format;
use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\block\tile\Tile;
use pocketmine\data\bedrock\BiomeIds;
use function array_fill;
use function array_map;
class Chunk{
@ -43,6 +41,8 @@ class Chunk{
public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE;
public const COORD_MASK = SubChunk::COORD_MASK;
private int $modificationCount;
/** @var int */
private $terrainDirtyFlags = 0;
@ -69,7 +69,7 @@ class Chunk{
/**
* @param SubChunk[] $subChunks
*/
public function __construct(array $subChunks = [], ?BiomeArray $biomeIds = null, ?HeightArray $heightMap = null){
public function __construct(array $subChunks, BiomeArray $biomeIds, bool $terrainPopulated, int $modificationCount = 0){
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
foreach($this->subChunks as $y => $null){
@ -77,8 +77,11 @@ class Chunk{
}
$val = ($this->subChunks->getSize() * SubChunk::EDGE_LENGTH);
$this->heightMap = $heightMap ?? new HeightArray(array_fill(0, 256, $val));
$this->biomeIds = $biomeIds ?? BiomeArray::fill(BiomeIds::OCEAN);
$this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this?
$this->biomeIds = $biomeIds;
$this->terrainPopulated = $terrainPopulated;
$this->modificationCount = $modificationCount;
}
/**
@ -107,6 +110,7 @@ class Chunk{
public function setFullBlock(int $x, int $y, int $z, int $block) : void{
$this->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)->setFullBlock($x, $y & SubChunk::COORD_MASK, $z, $block);
$this->terrainDirtyFlags |= self::DIRTY_FLAG_TERRAIN;
$this->modificationCount++;
}
/**
@ -170,6 +174,7 @@ class Chunk{
public function setBiomeId(int $x, int $z, int $biomeId) : void{
$this->biomeIds->set($x, $z, $biomeId);
$this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
$this->modificationCount++;
}
public function isLightPopulated() : ?bool{
@ -187,6 +192,7 @@ class Chunk{
public function setPopulated(bool $value = true) : void{
$this->terrainPopulated = $value;
$this->terrainDirtyFlags |= self::DIRTY_FLAG_TERRAIN;
$this->modificationCount++;
}
public function addTile(Tile $tile) : void{
@ -269,16 +275,24 @@ class Chunk{
}else{
$this->terrainDirtyFlags &= ~$flag;
}
$this->modificationCount++;
}
public function setTerrainDirty() : void{
$this->terrainDirtyFlags = ~0;
$this->modificationCount++;
}
public function clearTerrainDirtyFlags() : void{
$this->terrainDirtyFlags = 0;
}
/**
* Returns the modcount for this chunk. Any saveable change to the chunk will cause this number to be incremented,
* so you can use this to detect when the chunk has been modified.
*/
public function getModificationCount() : int{ return $this->modificationCount; }
public function getSubChunk(int $y) : SubChunk{
if($y < 0 || $y >= $this->subChunks->getSize()){
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");

View File

@ -51,6 +51,8 @@ final class FastChunkSerializer{
*/
public static function serializeTerrain(Chunk $chunk) : string{
$stream = new BinaryStream();
$stream->putLong($chunk->getModificationCount());
$stream->putByte(
($chunk->isPopulated() ? self::FLAG_POPULATED : 0)
);
@ -88,6 +90,7 @@ final class FastChunkSerializer{
*/
public static function deserializeTerrain(string $data) : Chunk{
$stream = new BinaryStream($data);
$modificationCounter = $stream->getLong();
$flags = $stream->getByte();
$terrainPopulated = (bool) ($flags & self::FLAG_POPULATED);
@ -115,10 +118,6 @@ final class FastChunkSerializer{
$biomeIds = new BiomeArray($stream->get(256));
$chunk = new Chunk($subChunks, $biomeIds);
$chunk->setPopulated($terrainPopulated);
$chunk->clearTerrainDirtyFlags();
return $chunk;
return new Chunk($subChunks, $biomeIds, $terrainPopulated, $modificationCounter);
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\world\format\io;
use pocketmine\utils\Filesystem;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\normal\Normal;
use pocketmine\world\WorldCreationOptions;
use Webmozart\PathUtil\Path;
use function basename;
@ -112,7 +113,9 @@ class FormatConverter{
Filesystem::recursiveUnlink($convertedOutput);
}
$this->newProvider->generate($convertedOutput, $data->getName(), WorldCreationOptions::create()
->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($data->getGenerator()))
//TODO: defaulting to NORMAL here really isn't very good behaviour, but it's consistent with what we already
//did previously; besides, WorldManager checks for unknown generators before this is reached anyway.
->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($data->getGenerator())?->getGeneratorClass() ?? Normal::class)
->setGeneratorOptions($data->getGeneratorOptions())
->setSeed($data->getSeed())
->setSpawnPosition($data->getSpawn())

View File

@ -39,6 +39,7 @@ use pocketmine\world\generator\Generator;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\World;
use pocketmine\world\WorldCreationOptions;
use Webmozart\PathUtil\Path;
use function file_get_contents;
use function file_put_contents;
use function strlen;
@ -101,7 +102,7 @@ class BedrockWorldData extends BaseNbtWorldData{
$nbt = new LittleEndianNbtSerializer();
$buffer = $nbt->write(new TreeRoot($worldData));
file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
file_put_contents(Path::join($path, "level.dat"), Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
}
protected function load() : CompoundTag{

View File

@ -33,6 +33,7 @@ use pocketmine\world\format\io\exception\CorruptedWorldException;
use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\World;
use pocketmine\world\WorldCreationOptions;
use Webmozart\PathUtil\Path;
use function ceil;
use function file_get_contents;
use function file_put_contents;
@ -68,7 +69,7 @@ class JavaWorldData extends BaseNbtWorldData{
$nbt = new BigEndianNbtSerializer();
$buffer = zlib_encode($nbt->write(new TreeRoot(CompoundTag::create()->setTag("Data", $worldData))), ZLIB_ENCODING_GZIP);
file_put_contents($path . "level.dat", $buffer);
file_put_contents(Path::join($path, "level.dat"), $buffer);
}
protected function load() : CompoundTag{

View File

@ -25,6 +25,7 @@ namespace pocketmine\world\format\io\leveldb;
use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
@ -404,20 +405,22 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
}
$chunk = new Chunk(
$subChunks,
$biomeArray
);
//TODO: tile ticks, biome states (?)
$finalisationChr = $this->db->get($index . self::TAG_STATE_FINALISATION);
if($finalisationChr !== false){
$finalisation = ord($finalisationChr);
$chunk->setPopulated($finalisation === self::FINALISATION_DONE);
$terrainPopulated = $finalisation === self::FINALISATION_DONE;
}else{ //older versions didn't have this tag
$chunk->setPopulated();
$terrainPopulated = true;
}
//TODO: tile ticks, biome states (?)
$chunk = new Chunk(
$subChunks,
$biomeArray ?? BiomeArray::fill(BiomeIds::OCEAN), //TODO: maybe missing biomes should be an error?
$terrainPopulated
);
if($hasBeenUpgraded){
$chunk->setTerrainDirty(); //trigger rewriting chunk to disk if it was converted from an older format
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\world\format\io\region;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\nbt\BigEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\ByteArrayTag;
@ -86,15 +87,16 @@ trait LegacyAnvilChunkTrait{
$biomeArray = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format
}elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){
$biomeArray = $makeBiomeArray($biomesTag->getValue());
}else{
$biomeArray = BiomeArray::fill(BiomeIds::OCEAN);
}
$result = new Chunk(
$subChunks,
$biomeArray
);
$result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0);
return new ChunkData(
$result,
new Chunk(
$subChunks,
$biomeArray,
$chunk->getByte("TerrainPopulated", 0) !== 0
),
($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [],
($tilesTag = $chunk->getTag("TileEntities")) instanceof ListTag ? self::getCompoundList("TileEntities", $tilesTag) : [],
);

View File

@ -25,6 +25,7 @@ namespace pocketmine\world\format\io\region;
use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\nbt\BigEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\ByteArrayTag;
@ -80,12 +81,16 @@ class McRegion extends RegionWorldProvider{
$biomeIds = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format
}elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){
$biomeIds = $makeBiomeArray($biomesTag->getValue());
}else{
$biomeIds = BiomeArray::fill(BiomeIds::OCEAN);
}
$result = new Chunk($subChunks, $biomeIds);
$result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0);
return new ChunkData(
$result,
new Chunk(
$subChunks,
$biomeIds,
$chunk->getByte("TerrainPopulated", 0) !== 0
),
($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [],
($tilesTag = $chunk->getTag("TileEntities")) instanceof ListTag ? self::getCompoundList("TileEntities", $tilesTag) : [],
);

View File

@ -24,47 +24,32 @@ declare(strict_types=1);
namespace pocketmine\world\generator;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\LegacyStringToItemParser;
use pocketmine\item\LegacyStringToItemParserException;
use pocketmine\world\ChunkManager;
use pocketmine\world\format\BiomeArray;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\SubChunk;
use pocketmine\world\generator\object\OreType;
use pocketmine\world\generator\populator\Ore;
use pocketmine\world\generator\populator\Populator;
use function array_map;
use function count;
use function explode;
use function preg_match;
use function preg_match_all;
class Flat extends Generator{
/** @var Chunk */
private $chunk;
/** @var Populator[] */
private $populators = [];
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private $structure;
/** @var int */
private $biome;
/**
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private array $options = [];
private FlatGeneratorOptions $options;
/**
* @throws InvalidGeneratorOptionsException
*/
public function __construct(int $seed, string $preset){
parent::__construct($seed, $preset !== "" ? $preset : "2;bedrock,2xdirt,grass;1;");
$this->parsePreset();
$this->options = FlatGeneratorOptions::parsePreset($this->preset);
if(isset($this->options["decoration"])){
if(isset($this->options->getExtraOptions()["decoration"])){
$ores = new Ore();
$stone = VanillaBlocks::STONE();
$ores->setOreTypes([
@ -83,76 +68,15 @@ class Flat extends Generator{
$this->generateBaseChunk();
}
/**
* @return int[]
* @phpstan-return array<int, int>
*
* @throws InvalidGeneratorOptionsException
*/
public static function parseLayers(string $layers) : array{
$result = [];
$split = array_map('\trim', explode(',', $layers));
$y = 0;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($split as $line){
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
if(count($matches) !== 3){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
}
$cnt = $matches[1] !== "" ? (int) $matches[1] : 1;
try{
$b = $itemParser->parse($matches[2])->getBlock();
}catch(LegacyStringToItemParserException $e){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\": " . $e->getMessage(), 0, $e);
}
for($cY = $y, $y += $cnt; $cY < $y; ++$cY){
$result[$cY] = $b->getFullId();
}
}
return $result;
}
protected function parsePreset() : void{
$preset = explode(";", $this->preset);
$blocks = $preset[1] ?? "";
$this->biome = (int) ($preset[2] ?? 1);
$options = $preset[3] ?? "";
$this->structure = self::parseLayers($blocks);
//TODO: more error checking
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
foreach($matches[2] as $i => $option){
$params = true;
if($matches[3][$i] !== ""){
$params = [];
$p = explode(" ", $matches[3][$i]);
foreach($p as $k){
$k = explode("=", $k);
if(isset($k[1])){
$params[$k[0]] = $k[1];
}
}
}
$this->options[$option] = $params;
}
}
protected function generateBaseChunk() : void{
$this->chunk = new Chunk();
$this->chunk = new Chunk([], BiomeArray::fill($this->options->getBiomeId()), false);
for($Z = 0; $Z < Chunk::EDGE_LENGTH; ++$Z){
for($X = 0; $X < Chunk::EDGE_LENGTH; ++$X){
$this->chunk->setBiomeId($X, $Z, $this->biome);
}
}
$count = count($this->structure);
$structure = $this->options->getStructure();
$count = count($structure);
for($sy = 0; $sy < $count; $sy += SubChunk::EDGE_LENGTH){
$subchunk = $this->chunk->getSubChunk($sy >> SubChunk::COORD_BIT_SIZE);
for($y = 0; $y < SubChunk::EDGE_LENGTH and isset($this->structure[$y | $sy]); ++$y){
$id = $this->structure[$y | $sy];
for($y = 0; $y < SubChunk::EDGE_LENGTH and isset($structure[$y | $sy]); ++$y){
$id = $structure[$y | $sy];
for($Z = 0; $Z < SubChunk::EDGE_LENGTH; ++$Z){
for($X = 0; $X < SubChunk::EDGE_LENGTH; ++$X){

View File

@ -0,0 +1,127 @@
<?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\world\generator;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\item\LegacyStringToItemParser;
use pocketmine\item\LegacyStringToItemParserException;
use function array_map;
use function count;
use function explode;
use function preg_match;
use function preg_match_all;
/**
* @internal
*/
final class FlatGeneratorOptions{
/**
* @param int[] $structure
* @param mixed[] $extraOptions
* @phpstan-param array<int, int> $structure
* @phpstan-param array<string, array<string, string>|true> $extraOptions
*/
public function __construct(
private array $structure,
private int $biomeId,
private array $extraOptions = []
){}
/**
* @return int[]
* @phpstan-return array<int, int>
*/
public function getStructure() : array{ return $this->structure; }
public function getBiomeId() : int{ return $this->biomeId; }
/**
* @return mixed[]
* @phpstan-return array<string, array<string, string>|true>
*/
public function getExtraOptions() : array{ return $this->extraOptions; }
/**
* @return int[]
* @phpstan-return array<int, int>
*
* @throws InvalidGeneratorOptionsException
*/
public static function parseLayers(string $layers) : array{
$result = [];
$split = array_map('\trim', explode(',', $layers));
$y = 0;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($split as $line){
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
if(count($matches) !== 3){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
}
$cnt = $matches[1] !== "" ? (int) $matches[1] : 1;
try{
$b = $itemParser->parse($matches[2])->getBlock();
}catch(LegacyStringToItemParserException $e){
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\": " . $e->getMessage(), 0, $e);
}
for($cY = $y, $y += $cnt; $cY < $y; ++$cY){
$result[$cY] = $b->getFullId();
}
}
return $result;
}
/**
* @throws InvalidGeneratorOptionsException
*/
public static function parsePreset(string $presetString) : self{
$preset = explode(";", $presetString);
$blocks = $preset[1] ?? "";
$biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS);
$optionsString = $preset[3] ?? "";
$structure = self::parseLayers($blocks);
$options = [];
//TODO: more error checking
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $optionsString, $matches);
foreach($matches[2] as $i => $option){
$params = true;
if($matches[3][$i] !== ""){
$params = [];
$p = explode(" ", $matches[3][$i]);
foreach($p as $k){
$k = explode("=", $k);
if(isset($k[1])){
$params[$k[0]] = $k[1];
}
}
}
$options[(string) $option] = $params;
}
return new self($structure, $biomeId, $options);
}
}

View File

@ -34,35 +34,49 @@ final class GeneratorManager{
use SingletonTrait;
/**
* @var string[] name => classname mapping
* @phpstan-var array<string, class-string<Generator>>
* @var GeneratorManagerEntry[] name => classname mapping
* @phpstan-var array<string, GeneratorManagerEntry>
*/
private $list = [];
public function __construct(){
$this->addGenerator(Flat::class, "flat");
$this->addGenerator(Normal::class, "normal");
$this->addGenerator(Normal::class, "default");
$this->addGenerator(Nether::class, "hell");
$this->addGenerator(Nether::class, "nether");
$this->addGenerator(Flat::class, "flat", \Closure::fromCallable(function(string $preset) : ?InvalidGeneratorOptionsException{
if($preset === ""){
return null;
}
try{
FlatGeneratorOptions::parsePreset($preset);
return null;
}catch(InvalidGeneratorOptionsException $e){
return $e;
}
}));
$this->addGenerator(Normal::class, "normal", fn() => null);
$this->addGenerator(Normal::class, "default", fn() => null);
$this->addGenerator(Nether::class, "hell", fn() => null);
$this->addGenerator(Nether::class, "nether", fn() => null);
}
/**
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
* @param string $name Alias for this generator type that can be written in configs
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
* @param string $name Alias for this generator type that can be written in configs
* @param \Closure $presetValidator Callback to validate generator options for new worlds
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
*
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
*
* @phpstan-param class-string<Generator> $class
*
* @throws \InvalidArgumentException
*/
public function addGenerator(string $class, string $name, bool $overwrite = false) : void{
public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false) : void{
Utils::testValidInstance($class, Generator::class);
if(!$overwrite and isset($this->list[$name = strtolower($name)])){
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
}
$this->list[$name] = $class;
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator);
}
/**
@ -75,24 +89,10 @@ final class GeneratorManager{
}
/**
* Returns a class name of a registered Generator matching the given name.
*
* @param bool $throwOnMissing @deprecated this is for backwards compatibility only
*
* @return string Name of class that extends Generator
* @phpstan-return class-string<Generator>
*
* @throws \InvalidArgumentException if the generator type isn't registered
* Returns the generator entry of a registered Generator matching the given name, or null if not found.
*/
public function getGenerator(string $name, bool $throwOnMissing = false){
if(isset($this->list[$name = strtolower($name)])){
return $this->list[$name];
}
if($throwOnMissing){
throw new \InvalidArgumentException("Alias \"$name\" does not map to any known generator");
}
return Normal::class;
public function getGenerator(string $name) : ?GeneratorManagerEntry{
return $this->list[strtolower($name)] ?? null;
}
/**
@ -106,7 +106,7 @@ final class GeneratorManager{
public function getGeneratorName(string $class) : string{
Utils::testValidInstance($class, Generator::class);
foreach($this->list as $name => $c){
if($c === $class){
if($c->getGeneratorClass() === $class){
return $name;
}
}

View File

@ -0,0 +1,48 @@
<?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\world\generator;
final class GeneratorManagerEntry{
/**
* @phpstan-param class-string<Generator> $generatorClass
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
*/
public function __construct(
private string $generatorClass,
private \Closure $presetValidator
){}
/** @phpstan-return class-string<Generator> */
public function getGeneratorClass() : string{ return $this->generatorClass; }
/**
* @throws InvalidGeneratorOptionsException
*/
public function validateGeneratorOptions(string $generatorOptions) : void{
if(($exception = ($this->presetValidator)($generatorOptions)) !== null){
throw $exception;
}
}
}

View File

@ -23,12 +23,17 @@ declare(strict_types=1);
namespace pocketmine\world\generator;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\scheduler\AsyncTask;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\format\BiomeArray;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\FastChunkSerializer;
use pocketmine\world\SimpleChunkManager;
use pocketmine\world\World;
use function array_map;
use function igbinary_serialize;
use function igbinary_unserialize;
use function intdiv;
class PopulationTask extends AsyncTask{
@ -44,25 +49,7 @@ class PopulationTask extends AsyncTask{
/** @var string|null */
public $chunk;
/** @var string|null */
public $chunk0;
/** @var string|null */
public $chunk1;
/** @var string|null */
public $chunk2;
/** @var string|null */
public $chunk3;
//center chunk
/** @var string|null */
public $chunk5;
/** @var string|null */
public $chunk6;
/** @var string|null */
public $chunk7;
/** @var string|null */
public $chunk8;
private string $adjacentChunks;
public function __construct(World $world, int $chunkX, int $chunkZ, ?Chunk $chunk){
$this->worldId = $world->getId();
@ -70,9 +57,10 @@ class PopulationTask extends AsyncTask{
$this->chunkZ = $chunkZ;
$this->chunk = $chunk !== null ? FastChunkSerializer::serializeTerrain($chunk) : null;
foreach($world->getAdjacentChunks($chunkX, $chunkZ) as $i => $c){
$this->{"chunk$i"} = $c !== null ? FastChunkSerializer::serializeTerrain($c) : null;
}
$this->adjacentChunks = igbinary_serialize(array_map(
fn(?Chunk $c) => $c !== null ? FastChunkSerializer::serializeTerrain($c) : null,
$world->getAdjacentChunks($chunkX, $chunkZ)
)) ?? throw new AssumptionFailedError("igbinary_serialize() returned null");
$this->storeLocal(self::TLS_KEY_WORLD, $world);
}
@ -85,25 +73,19 @@ class PopulationTask extends AsyncTask{
$generator = $context->getGenerator();
$manager = new SimpleChunkManager($context->getWorldMinY(), $context->getWorldMaxY());
/** @var Chunk[] $chunks */
$chunks = [];
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
for($i = 0; $i < 9; ++$i){
if($i === 4){
continue;
}
$ck = $this->{"chunk$i"};
if($ck === null){
$chunks[$i] = null;
}else{
$chunks[$i] = FastChunkSerializer::deserializeTerrain($ck);
}
}
/** @var string[] $serialChunks */
$serialChunks = igbinary_unserialize($this->adjacentChunks);
$chunks = array_map(
fn(?string $serialized) => $serialized !== null ? FastChunkSerializer::deserializeTerrain($serialized) : null,
$serialChunks
);
$oldModCounts = array_map(fn(?Chunk $chunk) => $chunk !== null ? $chunk->getModificationCount() : null, $chunks);
self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk);
/** @var Chunk[] $resultChunks */
$resultChunks = []; //this is just to keep phpstan's type inference happy
foreach($chunks as $i => $c){
$cX = (-1 + $i % 3) + $this->chunkX;
@ -121,13 +103,15 @@ class PopulationTask extends AsyncTask{
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
$serialChunks = [];
foreach($chunks as $i => $c){
$this->{"chunk$i"} = $c->isTerrainDirty() ? FastChunkSerializer::serializeTerrain($c) : null;
$serialChunks[$i] = $oldModCounts[$i] !== $c->getModificationCount() ? FastChunkSerializer::serializeTerrain($c) : null;
}
$this->adjacentChunks = igbinary_serialize($serialChunks) ?? throw new AssumptionFailedError("igbinary_serialize() returned null");
}
private static function setOrGenerateChunk(SimpleChunkManager $manager, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $chunk) : Chunk{
$manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk());
$manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], BiomeArray::fill(BiomeIds::OCEAN), false));
if($chunk === null){
$generator->generateChunk($manager, $chunkX, $chunkZ);
$chunk = $manager->getChunk($chunkX, $chunkZ);
@ -144,23 +128,26 @@ class PopulationTask extends AsyncTask{
/** @var World $world */
$world = $this->fetchLocal(self::TLS_KEY_WORLD);
if($world->isLoaded()){
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
$chunk = $this->chunk !== null ?
FastChunkSerializer::deserializeTerrain($this->chunk) :
throw new AssumptionFailedError("Center chunk should never be null");
for($i = 0; $i < 9; ++$i){
if($i === 4){
continue;
}
$c = $this->{"chunk$i"};
/**
* @var string[]|null[] $serialAdjacentChunks
* @phpstan-var array<int, string|null> $serialAdjacentChunks
*/
$serialAdjacentChunks = igbinary_unserialize($this->adjacentChunks);
$adjacentChunks = [];
foreach($serialAdjacentChunks as $i => $c){
if($c !== null){
$xx = -1 + $i % 3;
$zz = -1 + intdiv($i, 3);
$c = FastChunkSerializer::deserializeTerrain($c);
$world->generateChunkCallback($this->chunkX + $xx, $this->chunkZ + $zz, $c);
$adjacentChunks[World::chunkHash($this->chunkX + $xx, $this->chunkZ + $zz)] = FastChunkSerializer::deserializeTerrain($c);
}
}
$world->generateChunkCallback($this->chunkX, $this->chunkZ, $chunk);
$world->generateChunkCallback($this->chunkX, $this->chunkZ, $chunk, $adjacentChunks);
}
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\Block;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
class BlockBreakParticle implements Particle{
@ -38,6 +39,6 @@ class BlockBreakParticle implements Particle{
}
public function encode(Vector3 $pos) : array{
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_DESTROY, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()), $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_DESTROY, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()), $pos)];
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\Block;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
/**
* This particle appears when a player is attacking a block face in survival mode attempting to break it.
@ -44,6 +45,6 @@ class BlockPunchParticle implements Particle{
}
public function encode(Vector3 $pos) : array{
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()) | ($this->face << 24), $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_PUNCH_BLOCK, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()) | ($this->face << 24), $pos)];
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\world\particle;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
use function abs;
class DragonEggTeleportParticle implements Particle{
@ -57,6 +58,6 @@ class DragonEggTeleportParticle implements Particle{
(abs($this->yDiff) << 8) |
abs($this->zDiff);
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_DRAGON_EGG_TELEPORT, $data, $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_DRAGON_EGG_TELEPORT, $data, $pos)];
}
}

View File

@ -25,10 +25,11 @@ namespace pocketmine\world\particle;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
class EndermanTeleportParticle implements Particle{
public function encode(Vector3 $pos) : array{
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_ENDERMAN_TELEPORT, 0, $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_ENDERMAN_TELEPORT, 0, $pos)];
}
}

View File

@ -28,8 +28,10 @@ use pocketmine\entity\Skin;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
use pocketmine\network\mcpe\protocol\types\DeviceOS;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty;
@ -96,22 +98,31 @@ class FloatingTextParticle implements Particle{
$p[] = PlayerListPacket::add([PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, SkinAdapterSingleton::get()->toSkinData(new Skin("Standard_Custom", str_repeat("\x00", 8192))))]);
$pk = new AddPlayerPacket();
$pk->uuid = $uuid;
$pk->username = $name;
$pk->entityRuntimeId = $this->entityId;
$pk->position = $pos; //TODO: check offset
$pk->item = ItemStackWrapper::legacy(ItemStack::null());
$flags = (
$actorFlags = (
1 << EntityMetadataFlags::IMMOBILE
);
$pk->metadata = [
EntityMetadataProperties::FLAGS => new LongMetadataProperty($flags),
$actorMetadata = [
EntityMetadataProperties::FLAGS => new LongMetadataProperty($actorFlags),
EntityMetadataProperties::SCALE => new FloatMetadataProperty(0.01) //zero causes problems on debug builds
];
$p[] = $pk;
$p[] = AddPlayerPacket::create(
$uuid,
$name,
$this->entityId, //TODO: actor unique ID
$this->entityId,
"",
$pos, //TODO: check offset
null,
0,
0,
0,
ItemStackWrapper::legacy(ItemStack::null()),
$actorMetadata,
AdventureSettingsPacket::create(0, 0, 0, 0, 0, $this->entityId),
[],
"",
DeviceOS::UNKNOWN
);
$p[] = PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($uuid)]);
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\world\particle;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
class MobSpawnParticle implements Particle{
/** @var int */
@ -39,6 +40,6 @@ class MobSpawnParticle implements Particle{
}
public function encode(Vector3 $pos) : array{
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_SPAWN, ($this->width & 0xff) | (($this->height & 0xff) << 8), $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_SPAWN, ($this->width & 0xff) | (($this->height & 0xff) << 8), $pos)];
}
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\world\particle;
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
use pocketmine\network\mcpe\protocol\types\LevelEvent;
class PotionSplashParticle implements Particle{
@ -50,6 +51,6 @@ class PotionSplashParticle implements Particle{
}
public function encode(Vector3 $pos) : array{
return [LevelEventPacket::create(LevelEventPacket::EVENT_PARTICLE_SPLASH, $this->color->toARGB(), $pos)];
return [LevelEventPacket::create(LevelEvent::PARTICLE_SPLASH, $this->color->toARGB(), $pos)];
}
}

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