mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 11:16:57 +00:00
Compare commits
97 Commits
4.0.0-BETA
...
4.0.0-BETA
Author | SHA1 | Date | |
---|---|---|---|
428bd5ae91 | |||
19f448d074 | |||
1c18c731ef | |||
8a2ecfe1d4 | |||
089e62b44e | |||
32a34d2494 | |||
ee9f5e0044 | |||
88b7389080 | |||
f1cc168d26 | |||
fb5543a2ad | |||
a4eda9a8f5 | |||
eb75df6f8e | |||
c66790b6a6 | |||
d78801b9d5 | |||
d410db4302 | |||
a62ce64fdd | |||
5db3915aad | |||
eb40b741ae | |||
b3720b3f17 | |||
7effa03ba4 | |||
d0474ccd92 | |||
2b0768f720 | |||
dba148cfaa | |||
48f77abe7e | |||
bb05af103d | |||
0ef5c67b9b | |||
6d89265510 | |||
a7d8a598e1 | |||
51fbff204b | |||
1873457840 | |||
fca70efbb1 | |||
8f88393184 | |||
b9d9b69bbe | |||
1d99cd329a | |||
bd8cba1a7f | |||
24d4daec90 | |||
4178c81209 | |||
94f4ef5862 | |||
2e2515354c | |||
359d0835f3 | |||
d4cbde6f10 | |||
a5418a019d | |||
baba25953f | |||
d53347454b | |||
401e8d117b | |||
9835d75f65 | |||
b8519d1af4 | |||
f6e53f826b | |||
42ede30e77 | |||
04aedc6494 | |||
701a71a4ee | |||
e50072dc27 | |||
c77829f4ad | |||
c773e43eda | |||
c262c2e726 | |||
a4b65d6a3f | |||
986b4e0651 | |||
2971bf30a5 | |||
4f2bcb61d6 | |||
e2275cc8ec | |||
620874d902 | |||
44508a138f | |||
aa408c9a97 | |||
6d78a0b435 | |||
76b4b23d98 | |||
03fcd844eb | |||
fecc13f362 | |||
9646128d01 | |||
a788954551 | |||
dc07ac33d3 | |||
ec3986827c | |||
09c840b66a | |||
80b402e529 | |||
a3f8546ac4 | |||
46920818b5 | |||
69cb575789 | |||
fee6478cbe | |||
9c5cec77b1 | |||
f48b703533 | |||
70636f6eb4 | |||
c70b80c273 | |||
a794d24c81 | |||
8db5732b44 | |||
48f809d3fa | |||
0348236860 | |||
8c07748100 | |||
06e7338ff9 | |||
bdbfa70558 | |||
7a4af7a0bc | |||
34b1392598 | |||
321345fcc8 | |||
0ac9f4fe61 | |||
2db53775e0 | |||
8523f0fb0b | |||
b570324288 | |||
6284cd14c7 | |||
ce8af4e3bc |
13
.github/workflows/main.yml
vendored
13
.github/workflows/main.yml
vendored
@ -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
|
||||
@ -254,10 +253,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.9.0
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
with:
|
||||
php-version: 8.0
|
||||
tools: php-cs-fixer
|
||||
tools: php-cs-fixer:3.2
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: php-cs-fixer fix --dry-run --diff
|
||||
|
@ -61,6 +61,11 @@ return (new PhpCsFixer\Config)
|
||||
],
|
||||
'sort_algorithm' => 'alpha'
|
||||
],
|
||||
'phpdoc_line_span' => [
|
||||
'property' => 'single',
|
||||
'method' => null,
|
||||
'const' => null
|
||||
],
|
||||
'phpdoc_trim' => true,
|
||||
'phpdoc_trim_consecutive_blank_line_separation' => true,
|
||||
'single_import_per_statement' => true,
|
||||
|
@ -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();
|
||||
|
11
changelogs/3.25.md
Normal file
11
changelogs/3.25.md
Normal 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.
|
@ -1443,3 +1443,85 @@ Released 12th October 2021.
|
||||
### 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 28th 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
|
||||
|
||||
# 4.0.0-BETA8
|
||||
Released 29th October 2021.
|
||||
|
||||
## General
|
||||
- Chunk packet caches are now cleared by the memory manager on low memory.
|
||||
- `Entity->spawnTo()` now has an additional sanity check for matching worlds (might expose a few new errors in plugins).
|
||||
- [`pocketmine/math` 0.4.0](https://github.com/pmmp/Math/releases/tag/0.4.0) is now used. Please see its release notes for changes.
|
||||
|
||||
## Fixes
|
||||
- Zlib raw check for LevelDB is now done directly on startup, avoiding crashes when later trying to load worlds.
|
||||
- Fixed tiles and entities getting deleted from adjacent chunks during chunk population.
|
||||
- Fixed players being unable to open their inventories more than once.
|
||||
- Fixed entities not getting updated when a nearby chunk is replaced (e.g. dropped items would float in midair if the ground was lower than before)
|
||||
|
||||
## API
|
||||
### World
|
||||
- `World::setChunk()` has the following changes:
|
||||
- `$deleteEntitiesAndTiles` parameter has been removed.
|
||||
- Entities are no longer deleted on chunk replacement.
|
||||
- Tiles are no longer deleted on chunk replacement, unless one of the following conditions is met:
|
||||
- the target block in the new chunk doesn't expect a tile
|
||||
- the target block in the new chunk expects a different type of tile (responsibility of the plugin developer to create the new tile)
|
||||
- there's already a tile in the target chunk which conflicts with the old one
|
||||
- `Location::__construct()` has the following changes:
|
||||
- `world` parameter is now 4th instead of last.
|
||||
- All parameters are now mandatory.
|
||||
- Reverted addition of chunk modification counters in previous beta.
|
||||
- `Chunk::DIRTY_FLAG_TERRAIN` has been renamed to `Chunk::DIRTY_FLAG_BLOCKS`.
|
||||
|
@ -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",
|
||||
@ -42,14 +42,13 @@
|
||||
"pocketmine/errorhandler": "^0.3.0",
|
||||
"pocketmine/log": "^0.3.0",
|
||||
"pocketmine/log-pthreads": "^0.2.0",
|
||||
"pocketmine/math": "^0.3.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
"pocketmine/nbt": "^0.3.0",
|
||||
"pocketmine/raklib": "^0.14.2",
|
||||
"pocketmine/raklib-ipc": "^0.1.0",
|
||||
"pocketmine/snooze": "^0.3.0",
|
||||
"pocketmine/spl": "dev-master",
|
||||
"ramsey/uuid": "^4.1",
|
||||
"respect/validation": "^2.0",
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
|
269
composer.lock
generated
269
composer.lock
generated
@ -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": "c7a00c5a35d43f307fdba7a588029131",
|
||||
"content-hash": "3f467dca67940d465ceafbe5774d6977",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -249,25 +249,25 @@
|
||||
},
|
||||
{
|
||||
"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": "c8d891b4dff9817d5fcd373dfec0608b20be3b0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/faff7da904e68f69b1a9128956dac3122e87308a",
|
||||
"reference": "faff7da904e68f69b1a9128956dac3122e87308a",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c8d891b4dff9817d5fcd373dfec0608b20be3b0a",
|
||||
"reference": "c8d891b4dff9817d5fcd373dfec0608b20be3b0a",
|
||||
"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",
|
||||
"pocketmine/math": "^0.3.0 || ^0.4.0",
|
||||
"pocketmine/nbt": "^0.3.0",
|
||||
"ramsey/uuid": "^4.1"
|
||||
},
|
||||
@ -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-29T20:54:42+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",
|
||||
@ -592,26 +593,26 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/math",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Math.git",
|
||||
"reference": "83ec067b12c066fc61d9fb129daf7e61ef3b1d63"
|
||||
"reference": "6d64e2555bd2e95ed024574f75d1cefc135c89fc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Math/zipball/83ec067b12c066fc61d9fb129daf7e61ef3b1d63",
|
||||
"reference": "83ec067b12c066fc61d9fb129daf7e61ef3b1d63",
|
||||
"url": "https://api.github.com/repos/pmmp/Math/zipball/6d64e2555bd2e95ed024574f75d1cefc135c89fc",
|
||||
"reference": "6d64e2555bd2e95ed024574f75d1cefc135c89fc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"php": "^8.0",
|
||||
"php-64bit": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"irstea/phpunit-shim": "^8.5 || ^9.5",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.90",
|
||||
"phpstan/phpstan": "0.12.99",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.4"
|
||||
},
|
||||
"type": "library",
|
||||
@ -627,9 +628,9 @@
|
||||
"description": "PHP library containing math related code used in PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Math/issues",
|
||||
"source": "https://github.com/pmmp/Math/tree/0.3.0"
|
||||
"source": "https://github.com/pmmp/Math/tree/0.4.0"
|
||||
},
|
||||
"time": "2021-07-14T18:39:31+00:00"
|
||||
"time": "2021-10-29T20:33:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
@ -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,9 +1763,9 @@
|
||||
"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",
|
||||
@ -3693,6 +3491,7 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"pocketmine/bedrock-protocol": 20,
|
||||
"pocketmine/classloader": 20,
|
||||
"pocketmine/spl": 20
|
||||
},
|
||||
|
@ -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,
|
||||
|
Submodule resources/locale updated: 17fc6a1050...09c709f242
Submodule resources/vanilla updated: 19569dd729...f29b7be8fa
@ -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();
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\event\server\LowMemoryEvent;
|
||||
use pocketmine\network\mcpe\cache\ChunkCache;
|
||||
use pocketmine\scheduler\DumpWorkerMemoryTask;
|
||||
use pocketmine\scheduler\GarbageCollectionTask;
|
||||
use pocketmine\timings\Timings;
|
||||
@ -187,6 +188,7 @@ class MemoryManager{
|
||||
foreach($this->server->getWorldManager()->getWorlds() as $world){
|
||||
$world->clearCache(true);
|
||||
}
|
||||
ChunkCache::pruneCaches();
|
||||
}
|
||||
|
||||
if($this->lowMemChunkGC){
|
||||
|
@ -126,6 +126,9 @@ namespace pocketmine {
|
||||
if(version_compare($leveldb_version, "0.2.1") < 0){
|
||||
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
|
||||
}
|
||||
if(!defined('LEVELDB_ZLIB_RAW_COMPRESSION')){
|
||||
$messages[] = "Given version of php-leveldb doesn't support ZLIB_RAW compression (use https://github.com/pmmp/php-leveldb)";
|
||||
}
|
||||
}
|
||||
|
||||
$chunkutils2_version = phpversion("chunkutils2");
|
||||
|
242
src/Server.php
242
src/Server.php
@ -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;
|
||||
@ -250,6 +251,8 @@ class Server{
|
||||
/** @var Player[] */
|
||||
private array $playerList = [];
|
||||
|
||||
private SignalHandler $signalHandler;
|
||||
|
||||
/**
|
||||
* @var CommandSender[][]
|
||||
* @phpstan-var array<string, array<int, CommandSender>>
|
||||
@ -743,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,
|
||||
@ -965,115 +973,13 @@ class Server{
|
||||
$this->pluginManager->loadPlugins($this->pluginPath);
|
||||
$this->enablePlugins(PluginEnableOrder::STARTUP());
|
||||
|
||||
$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->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) && !$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;
|
||||
}
|
||||
$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));
|
||||
}
|
||||
|
||||
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->startupPrepareNetworkInterfaces()){
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
|
||||
@ -1111,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.
|
||||
@ -1326,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{
|
||||
@ -1450,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())));
|
||||
|
||||
|
@ -29,7 +29,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.0.0-BETA5";
|
||||
public const BASE_VERSION = "4.0.0-BETA8";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_NUMBER = 0;
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
|
@ -403,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));
|
||||
@ -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
|
||||
|
||||
|
@ -118,11 +118,9 @@ class Sapling extends Flowable{
|
||||
|
||||
$ev = new StructureGrowEvent($this, $transaction, $player);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
if(!$ev->isCancelled()){
|
||||
$transaction->apply();
|
||||
}
|
||||
|
||||
$transaction->apply();
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class TeleportCommand extends VanillaCommand{
|
||||
$x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]);
|
||||
$y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], 0, 256);
|
||||
$z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]);
|
||||
$targetLocation = new Location($x, $y, $z, $yaw, $pitch, $base->getWorld());
|
||||
$targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch);
|
||||
|
||||
$subject->teleport($targetLocation);
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success_coordinates(
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -1185,9 +1185,9 @@ abstract class Entity{
|
||||
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
|
||||
$this->boundingBox->minY - $this->ySize,
|
||||
($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2,
|
||||
$this->location->world,
|
||||
$this->location->yaw,
|
||||
$this->location->pitch,
|
||||
$this->location->world
|
||||
$this->location->pitch
|
||||
);
|
||||
|
||||
$this->getWorld()->onEntityMoved($this);
|
||||
@ -1418,23 +1418,27 @@ 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{
|
||||
if($player->getWorld() !== $this->getWorld()){
|
||||
throw new \InvalidArgumentException("Player is not in the same world");
|
||||
}
|
||||
$id = spl_object_id($player);
|
||||
//TODO: this will cause some visible lag during chunk resends; if the player uses a spawn egg in a chunk, the
|
||||
//created entity won't be visible until after the resend arrives. However, this is better than possibly crashing
|
||||
|
@ -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())]);
|
||||
|
@ -34,7 +34,7 @@ class Location extends Position{
|
||||
/** @var float */
|
||||
public $pitch;
|
||||
|
||||
public function __construct(float $x, float $y, float $z, float $yaw = 0.0, float $pitch = 0.0, ?World $world = null){
|
||||
public function __construct(float $x, float $y, float $z, ?World $world, float $yaw, float $pitch){
|
||||
$this->yaw = $yaw;
|
||||
$this->pitch = $pitch;
|
||||
parent::__construct($x, $y, $z, $world);
|
||||
@ -44,14 +44,14 @@ class Location extends Position{
|
||||
* @return Location
|
||||
*/
|
||||
public static function fromObject(Vector3 $pos, ?World $world, float $yaw = 0.0, float $pitch = 0.0){
|
||||
return new Location($pos->x, $pos->y, $pos->z, $yaw, $pitch, $world ?? (($pos instanceof Position) ? $pos->world : null));
|
||||
return new Location($pos->x, $pos->y, $pos->z, $world ?? (($pos instanceof Position) ? $pos->world : null), $yaw, $pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Location instance
|
||||
*/
|
||||
public function asLocation() : Location{
|
||||
return new Location($this->x, $this->y, $this->z, $this->yaw, $this->pitch, $this->world);
|
||||
return new Location($this->x, $this->y, $this->z, $this->world, $this->yaw, $this->pitch);
|
||||
}
|
||||
|
||||
public function getYaw() : float{
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -2075,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, []);
|
||||
}
|
||||
|
@ -430,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";
|
||||
|
@ -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,27 +165,22 @@ 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){
|
||||
switch(true){
|
||||
case $inv instanceof LoomInventory:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::LOOM, $inv->getHolder())];
|
||||
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())],
|
||||
$blockPosition = BlockPosition::fromVector3($inv->getHolder());
|
||||
$windowType = match(true){
|
||||
$inv instanceof LoomInventory => WindowTypes::LOOM,
|
||||
$inv instanceof FurnaceInventory => match($inv->getFurnaceType()->id()){
|
||||
FurnaceType::FURNACE()->id() => WindowTypes::FURNACE,
|
||||
FurnaceType::BLAST_FURNACE()->id() => WindowTypes::BLAST_FURNACE,
|
||||
FurnaceType::SMOKER()->id() => WindowTypes::SMOKER,
|
||||
default => throw new AssumptionFailedError("Unreachable")
|
||||
};
|
||||
case $inv instanceof EnchantInventory:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ENCHANTMENT, $inv->getHolder())];
|
||||
case $inv instanceof BrewingStandInventory:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::BREWING_STAND, $inv->getHolder())];
|
||||
case $inv instanceof AnvilInventory:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::ANVIL, $inv->getHolder())];
|
||||
case $inv instanceof HopperInventory:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::HOPPER, $inv->getHolder())];
|
||||
default:
|
||||
return [ContainerOpenPacket::blockInvVec3($id, WindowTypes::CONTAINER, $inv->getHolder())];
|
||||
}
|
||||
},
|
||||
$inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT,
|
||||
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
|
||||
$inv instanceof AnvilInventory => WindowTypes::ANVIL,
|
||||
$inv instanceof HopperInventory => WindowTypes::HOPPER,
|
||||
default => WindowTypes::CONTAINER
|
||||
};
|
||||
return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -279,6 +275,7 @@ class InventoryManager{
|
||||
$this->player->getId(),
|
||||
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand())),
|
||||
$selected,
|
||||
$selected,
|
||||
ContainerIds::INVENTORY
|
||||
));
|
||||
$this->clientSelectedHotbarSlot = $selected;
|
||||
|
@ -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;
|
||||
@ -446,6 +447,8 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
public function getPacketSerializerContext() : PacketSerializerContext{ return $this->packetSerializerContext; }
|
||||
|
||||
public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
|
||||
|
||||
public function getCompressor() : Compressor{
|
||||
@ -726,16 +729,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 +752,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 +779,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 +798,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 +834,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 +863,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 +974,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{
|
||||
|
@ -23,10 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\network\mcpe\compression\Compressor;
|
||||
use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\Server;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -40,35 +37,40 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||
}
|
||||
|
||||
public function broadcastPackets(array $recipients, array $packets) : void{
|
||||
//TODO: we should be using session-specific serializer contexts for this
|
||||
$stream = PacketBatch::fromPackets(new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()), ...$packets);
|
||||
|
||||
/** @var Compressor[] $compressors */
|
||||
$buffers = [];
|
||||
$compressors = [];
|
||||
/** @var NetworkSession[][] $compressorTargets */
|
||||
$compressorTargets = [];
|
||||
$targetMap = [];
|
||||
foreach($recipients as $recipient){
|
||||
$compressor = $recipient->getCompressor();
|
||||
$compressorId = spl_object_id($compressor);
|
||||
$serializerContext = $recipient->getPacketSerializerContext();
|
||||
$bufferId = spl_object_id($serializerContext);
|
||||
if(!isset($buffers[$bufferId])){
|
||||
$buffers[$bufferId] = PacketBatch::fromPackets($serializerContext, ...$packets);
|
||||
}
|
||||
|
||||
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
|
||||
$compressors[$compressorId] = $compressor;
|
||||
$compressorTargets[$compressorId][] = $recipient;
|
||||
$compressor = $recipient->getCompressor();
|
||||
$compressors[spl_object_id($compressor)] = $compressor;
|
||||
|
||||
$targetMap[$bufferId][spl_object_id($compressor)][] = $recipient;
|
||||
}
|
||||
|
||||
foreach($compressors as $compressorId => $compressor){
|
||||
if(!$compressor->willCompress($stream->getBuffer())){
|
||||
foreach($compressorTargets[$compressorId] as $target){
|
||||
foreach($packets as $pk){
|
||||
$target->addToSendBuffer($pk);
|
||||
foreach($targetMap as $bufferId => $compressorMap){
|
||||
$buffer = $buffers[$bufferId];
|
||||
foreach($compressorMap as $compressorId => $compressorTargets){
|
||||
$compressor = $compressors[$compressorId];
|
||||
if(!$compressor->willCompress($buffer->getBuffer())){
|
||||
foreach($compressorTargets as $target){
|
||||
foreach($packets as $pk){
|
||||
$target->addToSendBuffer($pk);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$promise = $this->server->prepareBatch($buffer, $compressor);
|
||||
foreach($compressorTargets as $target){
|
||||
$target->queueCompressed($promise);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$promise = $this->server->prepareBatch($stream, $compressor);
|
||||
foreach($compressorTargets[$compressorId] as $target){
|
||||
$target->queueCompressed($promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
16
src/network/mcpe/cache/ChunkCache.php
vendored
16
src/network/mcpe/cache/ChunkCache.php
vendored
@ -36,9 +36,6 @@ use function strlen;
|
||||
|
||||
/**
|
||||
* This class is used by the current MCPE protocol system to store cached chunk packets for fast resending.
|
||||
*
|
||||
* TODO: make MemoryManager aware of this so the cache can be destroyed when memory is low
|
||||
* TODO: this needs a hook for world unloading
|
||||
*/
|
||||
class ChunkCache implements ChunkListener{
|
||||
/** @var self[][] */
|
||||
@ -69,6 +66,19 @@ class ChunkCache implements ChunkListener{
|
||||
return self::$instances[$worldId][$compressorId];
|
||||
}
|
||||
|
||||
public static function pruneCaches() : void{
|
||||
foreach(self::$instances as $compressorMap){
|
||||
foreach($compressorMap as $chunkCache){
|
||||
foreach($chunkCache->caches as $chunkHash => $promise){
|
||||
if($promise->hasResult()){
|
||||
//Do not clear promises that are not yet fulfilled; they will have requesters waiting on them
|
||||
unset($chunkCache->caches[$chunkHash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var World */
|
||||
private $world;
|
||||
/** @var Compressor */
|
||||
|
11
src/network/mcpe/cache/CraftingDataCache.php
vendored
11
src/network/mcpe/cache/CraftingDataCache.php
vendored
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +175,12 @@ final class ItemTranslator{
|
||||
/**
|
||||
* @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];
|
||||
@ -188,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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
@ -391,7 +394,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
|
||||
public function spawnTo(Player $player) : void{
|
||||
if($this->isAlive() and $player->isAlive() and $player->getWorld() === $this->getWorld() and $player->canSee($this) and !$this->isSpectator()){
|
||||
if($this->isAlive() and $player->isAlive() and $player->canSee($this) and !$this->isSpectator()){
|
||||
parent::spawnTo($player);
|
||||
}
|
||||
}
|
||||
@ -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{
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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[]
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
42
src/plugin/PluginLoadTriage.php
Normal file
42
src/plugin/PluginLoadTriage.php
Normal 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 = [];
|
||||
}
|
108
src/plugin/PluginLoadabilityChecker.php
Normal file
108
src/plugin/PluginLoadabilityChecker.php
Normal 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;
|
||||
}
|
||||
}
|
@ -33,8 +33,6 @@ use pocketmine\event\plugin\PluginDisableEvent;
|
||||
use pocketmine\event\plugin\PluginEnableEvent;
|
||||
use pocketmine\event\RegisteredListener;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\permission\PermissionParser;
|
||||
@ -42,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;
|
||||
@ -52,17 +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 sprintf;
|
||||
use function stripos;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
|
||||
@ -80,6 +79,8 @@ class PluginManager{
|
||||
/** @var Plugin[] */
|
||||
protected $enabledPlugins = [];
|
||||
|
||||
private bool $loadPluginsGuard = false;
|
||||
|
||||
/**
|
||||
* @var PluginLoader[]
|
||||
* @phpstan-var array<class-string<PluginLoader>, PluginLoader>
|
||||
@ -131,63 +132,6 @@ class PluginManager{
|
||||
return Path::join(dirname($pluginPath), $pluginName);
|
||||
}
|
||||
|
||||
private function checkPluginLoadability(PluginDescription $description) : Translatable|string|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->server->getApiVersion(), $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));
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
$description->checkRequiredExtensions();
|
||||
}catch(PluginException $ex){
|
||||
return $ex->getMessage();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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->internalLoadPlugin($path, $loader, $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())));
|
||||
@ -265,18 +209,8 @@ class PluginManager{
|
||||
/**
|
||||
* @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){
|
||||
@ -288,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");
|
||||
@ -315,12 +259,12 @@ class PluginManager{
|
||||
|
||||
$name = $description->getName();
|
||||
|
||||
if(($loadabilityError = $this->checkPluginLoadability($description)) !== null){
|
||||
if(($loadabilityError = $loadabilityChecker->check($description)) !== null){
|
||||
$this->server->getLogger()->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, $loadabilityError)));
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -336,80 +280,138 @@ class PluginManager{
|
||||
)));
|
||||
continue;
|
||||
}
|
||||
$plugins[$name] = new PluginLoadTriageEntry($file, $loader, $description);
|
||||
|
||||
$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 => $entry){
|
||||
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++;
|
||||
|
||||
$oldRegisteredLoaders = $this->fileAssociations;
|
||||
if(($plugin = $this->internalLoadPlugin($entry->getFile(), $entry->getLoader(), $entry->getDescription())) instanceof Plugin){
|
||||
$loadedPlugins[$name] = $plugin;
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -24,15 +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 implode;
|
||||
use function is_int;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function trim;
|
||||
use const PHP_EOL;
|
||||
use const PTHREADS_INHERIT_NONE;
|
||||
|
||||
@ -137,46 +132,11 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
|
||||
* @return void
|
||||
*/
|
||||
public function logException(\Throwable $e, $trace = null){
|
||||
if($trace === null){
|
||||
$trace = $e->getTrace();
|
||||
}
|
||||
|
||||
$lines = [self::printExceptionMessage($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::printExceptionMessage($prev);
|
||||
foreach(Utils::printableTrace($prev->getTrace()) as $line){
|
||||
$lines[] = " " . $line;
|
||||
}
|
||||
}
|
||||
$lines[] = "--- End of exception information ---";
|
||||
$this->critical(implode("\n", $lines));
|
||||
$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:
|
||||
|
80
src/utils/SignalHandler.php
Normal file
80
src/utils/SignalHandler.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
@ -952,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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1603,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]);
|
||||
@ -1613,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);
|
||||
}
|
||||
|
||||
@ -2084,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 = [];
|
||||
@ -2117,88 +2124,36 @@ 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
|
||||
*/
|
||||
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk, bool $deleteEntitiesAndTiles = true) : void{
|
||||
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$oldChunk = $this->loadChunk($chunkX, $chunkZ);
|
||||
if($oldChunk !== null and $oldChunk !== $chunk){
|
||||
if($deleteEntitiesAndTiles){
|
||||
foreach($this->getChunkEntities($chunkX, $chunkZ) as $entity){
|
||||
if(!($entity instanceof Player)){
|
||||
$entity->close();
|
||||
}
|
||||
}
|
||||
foreach($oldChunk->getTiles() as $tile){
|
||||
$tile->close();
|
||||
}
|
||||
}else{
|
||||
foreach($oldChunk->getTiles() as $tile){
|
||||
$chunk->addTile($tile);
|
||||
$oldChunk->removeTile($tile);
|
||||
$deletedTiles = 0;
|
||||
$transferredTiles = 0;
|
||||
foreach($oldChunk->getTiles() as $oldTile){
|
||||
$tilePosition = $oldTile->getPosition();
|
||||
$localX = $tilePosition->getFloorX() & Chunk::COORD_MASK;
|
||||
$localY = $tilePosition->getFloorY();
|
||||
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
|
||||
|
||||
$newBlock = BlockFactory::getInstance()->fromFullBlock($chunk->getFullBlock($localX, $localY, $localZ));
|
||||
$expectedTileClass = $newBlock->getIdInfo()->getTileClass();
|
||||
if(
|
||||
$expectedTileClass === null || //new block doesn't expect a tile
|
||||
!($oldTile instanceof $expectedTileClass) || //new block expects a different tile
|
||||
(($newTile = $chunk->getTile($localX, $localY, $localZ)) !== null && $newTile !== $oldTile) //new chunk already has a different tile
|
||||
){
|
||||
$oldTile->close();
|
||||
$deletedTiles++;
|
||||
}else{
|
||||
$transferredTiles++;
|
||||
$chunk->addTile($oldTile);
|
||||
$oldChunk->removeTile($oldTile);
|
||||
}
|
||||
}
|
||||
if($deletedTiles > 0 || $transferredTiles > 0){
|
||||
$this->logger->debug("Replacement of chunk $chunkX $chunkZ caused deletion of $deletedTiles obsolete/conflicted tiles, and transfer of $transferredTiles");
|
||||
}
|
||||
}
|
||||
|
||||
$this->chunks[$chunkHash] = $chunk;
|
||||
@ -2222,6 +2177,14 @@ class World implements ChunkManager{
|
||||
$listener->onChunkChanged($chunkX, $chunkZ, $chunk);
|
||||
}
|
||||
}
|
||||
|
||||
for($cX = -1; $cX <= 1; ++$cX){
|
||||
for($cZ = -1; $cZ <= 1; ++$cZ){
|
||||
foreach($this->getChunkEntities($chunkX + $cX, $chunkZ + $cZ) as $entity){
|
||||
$entity->onNearbyBlockChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2769,6 +2732,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).
|
||||
@ -2853,6 +2837,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);
|
||||
|
||||
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();
|
||||
|
||||
|
@ -29,12 +29,10 @@ 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{
|
||||
public const DIRTY_FLAG_TERRAIN = 1 << 0;
|
||||
public const DIRTY_FLAG_BLOCKS = 1 << 0;
|
||||
public const DIRTY_FLAG_BIOMES = 1 << 3;
|
||||
|
||||
public const MAX_SUBCHUNKS = 16;
|
||||
@ -69,7 +67,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){
|
||||
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
|
||||
|
||||
foreach($this->subChunks as $y => $null){
|
||||
@ -77,8 +75,10 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +106,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->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,7 +186,7 @@ class Chunk{
|
||||
|
||||
public function setPopulated(bool $value = true) : void{
|
||||
$this->terrainPopulated = $value;
|
||||
$this->terrainDirtyFlags |= self::DIRTY_FLAG_TERRAIN;
|
||||
$this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
|
||||
}
|
||||
|
||||
public function addTile(Tile $tile) : void{
|
||||
@ -295,7 +295,7 @@ class Chunk{
|
||||
}
|
||||
|
||||
$this->subChunks[$y] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []);
|
||||
$this->setTerrainDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
|
||||
$this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,10 +115,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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
@ -433,7 +436,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$write->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
|
||||
|
||||
$chunk = $chunkData->getChunk();
|
||||
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN)){
|
||||
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)){
|
||||
$subChunks = $chunk->getSubChunks();
|
||||
foreach($subChunks as $y => $subChunk){
|
||||
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
|
||||
|
@ -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) : [],
|
||||
);
|
||||
|
@ -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) : [],
|
||||
);
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
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;
|
||||
@ -68,14 +69,7 @@ class Flat extends Generator{
|
||||
}
|
||||
|
||||
protected function generateBaseChunk() : void{
|
||||
$this->chunk = new Chunk();
|
||||
|
||||
$biomeId = $this->options->getBiomeId();
|
||||
for($Z = 0; $Z < Chunk::EDGE_LENGTH; ++$Z){
|
||||
for($X = 0; $X < Chunk::EDGE_LENGTH; ++$X){
|
||||
$this->chunk->setBiomeId($X, $Z, $biomeId);
|
||||
}
|
||||
}
|
||||
$this->chunk = new Chunk([], BiomeArray::fill($this->options->getBiomeId()), false);
|
||||
|
||||
$structure = $this->options->getStructure();
|
||||
$count = count($structure);
|
||||
|
@ -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,18 @@ 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
|
||||
);
|
||||
|
||||
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,20 +102,22 @@ 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] = $c->isTerrainDirty() ? 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);
|
||||
if($chunk === null){
|
||||
throw new AssumptionFailedError("We just set this chunk, so it must exist");
|
||||
}
|
||||
$chunk->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
|
||||
$chunk->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS, true);
|
||||
$chunk->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES, true);
|
||||
}
|
||||
return $chunk;
|
||||
@ -144,23 +127,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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)]);
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class AnvilBreakSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_ANVIL_BREAK, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ANVIL_BREAK, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class AnvilFallSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_ANVIL_FALL, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ANVIL_FALL, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class AnvilUseSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_ANVIL_USE, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ANVIL_USE, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ArrowHitSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BOW_HIT, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BOW_HIT, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BarrelCloseSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BLOCK_BARREL_CLOSE, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BLOCK_BARREL_CLOSE, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BarrelOpenSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BLOCK_BARREL_OPEN, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BLOCK_BARREL_OPEN, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
final class BellRingSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BLOCK_BELL_HIT, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BLOCK_BELL_HIT, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class BlazeShootSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_BLAZE_SHOOT, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_BLAZE_SHOOT, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\Block;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BlockBreakSound implements Sound{
|
||||
|
||||
@ -37,7 +38,7 @@ class BlockBreakSound implements Sound{
|
||||
$this->block = $block;
|
||||
}
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BREAK, $pos, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()))];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BREAK, $pos, false, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()))];
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\Block;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BlockPlaceSound implements Sound{
|
||||
|
||||
@ -37,7 +38,7 @@ class BlockPlaceSound implements Sound{
|
||||
$this->block = $block;
|
||||
}
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_PLACE, $pos, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()))];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::PLACE, $pos, false, RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId()))];
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\Block;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
/**
|
||||
* Played when a player attacks a block in survival, attempting to break it.
|
||||
@ -40,10 +41,11 @@ class BlockPunchSound implements Sound{
|
||||
$this->block = $block;
|
||||
}
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(
|
||||
LevelSoundEventPacket::SOUND_HIT,
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(
|
||||
LevelSoundEvent::HIT,
|
||||
$pos,
|
||||
false,
|
||||
RuntimeBlockMapping::getInstance()->toRuntimeId($this->block->getFullId())
|
||||
)];
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BowShootSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BOW, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BOW, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BucketEmptyLavaSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BUCKET_EMPTY_LAVA, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BUCKET_EMPTY_LAVA, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BucketEmptyWaterSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BUCKET_EMPTY_WATER, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BUCKET_EMPTY_WATER, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BucketFillLavaSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BUCKET_FILL_LAVA, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BUCKET_FILL_LAVA, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class BucketFillWaterSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_BUCKET_FILL_WATER, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BUCKET_FILL_WATER, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ChestCloseSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_CHEST_CLOSED, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::CHEST_CLOSED, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class ChestOpenSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_CHEST_OPEN, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::CHEST_OPEN, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class ClickSound implements Sound{
|
||||
|
||||
@ -39,7 +40,7 @@ class ClickSound implements Sound{
|
||||
return $this->pitch;
|
||||
}
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_CLICK, (int) ($this->pitch * 1000), $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_CLICK, (int) ($this->pitch * 1000), $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class DoorBumpSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_DOOR_BUMP, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_DOOR_BUMP, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class DoorCrashSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_DOOR_CRASH, 0, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_DOOR_CRASH, 0, $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class DoorSound implements Sound{
|
||||
|
||||
@ -39,7 +40,7 @@ class DoorSound implements Sound{
|
||||
return $this->pitch;
|
||||
}
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEventPacket::EVENT_SOUND_DOOR, (int) ($this->pitch * 1000), $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_DOOR, (int) ($this->pitch * 1000), $pos)];
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class EnderChestCloseSound implements Sound{
|
||||
|
||||
public function encode(?Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::create(LevelSoundEventPacket::SOUND_ENDERCHEST_CLOSED, $pos)];
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ENDERCHEST_CLOSED, $pos, false)];
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user