mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-13 21:05:08 +00:00
Compare commits
85 Commits
4.10.0
...
4.11.0-BET
Author | SHA1 | Date | |
---|---|---|---|
bf7014e0ec | |||
824ed0a56a | |||
b3ccf41307 | |||
a39938e6b6 | |||
d5bf88acc0 | |||
2d0602d19f | |||
3a2a23b236 | |||
1a8c8af523 | |||
1e9d83f014 | |||
6153a2ac70 | |||
ed452b9ccd | |||
c19880e045 | |||
cdbdcb5d67 | |||
29301614e8 | |||
2fdc46c165 | |||
bfd1b2c635 | |||
1671405cd0 | |||
fe982c697b | |||
1572b31b8d | |||
b6f6671a81 | |||
6da467b142 | |||
44af519cd6 | |||
fb31e6085e | |||
e4548da173 | |||
9a0ead6deb | |||
7864294336 | |||
b74c092d9b | |||
7bcc663b60 | |||
b3bda788d9 | |||
2cc8a56e68 | |||
57deb60355 | |||
6ae7cb288e | |||
3feaa18f6c | |||
41970feb57 | |||
0c7f8470b9 | |||
d6bbf8217d | |||
bda0ca23b4 | |||
b21cd82e94 | |||
1c7b1e9e5d | |||
b87e4d8bd3 | |||
86a2f8e360 | |||
cfb0cad7e0 | |||
83e5b0adb6 | |||
a7dfa0907c | |||
83a136a176 | |||
e4fc523251 | |||
7d29ac8293 | |||
89e29448ee | |||
9f97654f6f | |||
441b06f6c7 | |||
ffb3af3e0d | |||
b3f03d7ae6 | |||
2585160ca2 | |||
14d3e6c7d5 | |||
65ec318c30 | |||
a25cb3741a | |||
5b89833d5c | |||
c16893cbac | |||
5d5366a7c8 | |||
d6af2b12f4 | |||
ad2d59923c | |||
792c1b62b7 | |||
e90abecf38 | |||
4d34885b15 | |||
f7ab0a3b92 | |||
16ed16722a | |||
42f9336f7a | |||
2940547026 | |||
24e72ec109 | |||
dbc0b9634b | |||
040516054f | |||
4e3964ffce | |||
93254523e6 | |||
e00f8e3a32 | |||
e2855aadff | |||
4d6ec66270 | |||
f1a63098bd | |||
4b1052022c | |||
5a8983dd81 | |||
38651fde74 | |||
63ee03a7be | |||
088a2e478c | |||
35fd71eddf | |||
be168beba0 | |||
4b73bedd57 |
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
uses: shivammathur/setup-php@2.22.0
|
||||
with:
|
||||
php-version: 8.0
|
||||
|
||||
|
2
.github/workflows/draft-release.yml
vendored
2
.github/workflows/draft-release.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
uses: shivammathur/setup-php@2.22.0
|
||||
with:
|
||||
php-version: 8.0
|
||||
|
||||
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -195,7 +195,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
uses: shivammathur/setup-php@2.22.0
|
||||
with:
|
||||
php-version: 8.0
|
||||
tools: php-cs-fixer:3.11
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\build\generate_known_translation_apis;
|
||||
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function dirname;
|
||||
|
@ -11,4 +11,28 @@ Released 26th October 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.40.
|
||||
- Removed support for older versions.
|
||||
- Removed support for older versions.
|
||||
|
||||
## Fixes
|
||||
- Fixed incorrect command descriptions showing in `/help` when multiple commands use the same name. Previously, the most recently registered command would show, even though it wouldn't actually be invoked.
|
||||
- Fixed splash potions affecting players in spectator mode.
|
||||
- Fixed `World->addParticle()` sending particles to players who couldn't possibly see them when a list of targets was used.
|
||||
- Fixed `World->addSound()` sending sounds to players who couldn't possibly hear them when a list of targets was used.
|
||||
|
||||
## Documentation
|
||||
- Improved type information available for various API methods in `World`.
|
||||
|
||||
# 4.10.1
|
||||
Released 7th November 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed spawning in the void if spawn terrain in a world is solid at the default spawn position.
|
||||
- Fixed totems of undying activating when the player has 1 HP remaining.
|
||||
- Fixed durable items such as tools becoming unbreakable when in stacks larger than 1. Now, the durability correctly resets when the tool breaks.
|
||||
- TPS below 12 now correctly shows as red in `/status`. Previously, it showed as orange due to a condition ordering bug.
|
||||
- Improved handling of missing arguments in user-defined `pocketmine.yml` command aliases. Previously, missing arguments would be filled with an empty string, which caused a variety of unexpected behaviour.
|
||||
|
||||
## Internals
|
||||
- Added validation for the array given to `BaseInventory->setContents()` to ensure that it contains only `Item` instances.
|
||||
- Silenced `PlayerAuthInputPacket` spam when the session is in the "spawn response" state.
|
||||
- Updated to PHPStan 1.9.
|
||||
|
67
changelogs/4.11-beta.md
Normal file
67
changelogs/4.11-beta.md
Normal file
@ -0,0 +1,67 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.40**
|
||||
|
||||
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.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.
|
||||
|
||||
# 4.11.0-BETA1
|
||||
Released 7th November 2022.
|
||||
|
||||
## General
|
||||
- Packet receive timings have now been split into two subcategories - Decode and Handle.
|
||||
- Console command entry can now be disabled via the `console.enable-input` setting in `pocketmine.yml`.
|
||||
- Best suited for headless servers (e.g. in a Docker container) where the console will never be used anyway.
|
||||
- Disabling the console reader slightly reduces memory usage, because console reading currently requires an additional subprocess.
|
||||
- Console command output now appears on the terminal only, and is not written to the log file.
|
||||
- The output from console commands now appears with a `Command output |` prefix, instead of as a log message.
|
||||
- Introduced validation for the `--data` and `--plugins` command line options.
|
||||
- Encrypted resource packs are now supported, by means of adding a `.key` file alongside the pack in the `resource_packs` folder.
|
||||
- e.g. `MyEncryptedPack.zip` -> `MyEncryptedPack.zip.key`
|
||||
|
||||
## Gameplay
|
||||
- Fixed supporting blocks of dead bush to be in line with vanilla.
|
||||
- Sugarcane can now be grown using bonemeal on any part of the sugarcane. Previously, it only worked when used on the bottom block.
|
||||
- Fixed modifier values for Instant Damage and Regeneration effects.
|
||||
|
||||
## API
|
||||
### General
|
||||
- Plugins are now always disabled before their dependencies, to ensure that they are able to shutdown properly (e.g. a core plugin depending on a database plugin may want to save data to a DB during `onDisable()`).
|
||||
- [`webmozart/path-util`](https://packagist.org/packages/webmozart/path-util) has been deprecated, and will be dropped in favour of [`symfony/filesystem`](https://packagist.org/packages/symfony/filesystem) in PM5.
|
||||
- To prepare for this change, simply replace any usage of `Webmozart\PathUtil\Path` with `Symfony\Component\Filesystem\Path`, which is available as a dependency in this release.
|
||||
|
||||
### `pocketmine`
|
||||
- The following API methods are now deprecated:
|
||||
- `Server->getPlayerByPrefix()`
|
||||
|
||||
### `pocketmine\entity`
|
||||
- `EntitySpawnEvent` and `ItemSpawnEvent` are now fired on the first tick after the entity is added to the world. Previously, these events were called directly from the entity constructor, making it impossible to get properties like velocity which are often set after the entity is created.
|
||||
- The following API methods are now deprecated:
|
||||
- `Living->hasLineOfSight()`
|
||||
|
||||
### `pocketmine\item`
|
||||
- The following new API methods have been added:
|
||||
- `public Armor->clearCustomColor() : $this`
|
||||
|
||||
### `pocketmine\inventory\transaction`
|
||||
- Introduced a `TransactionBuilder` class. This makes it less of a hassle to build an `InventoryTransaction` server-side, since the regular `Inventory` API methods can be used, rather than having to manually create `SlotChangeAction`s.
|
||||
|
||||
### `pocketmine\player`
|
||||
- The following new API methods have been added:
|
||||
- `public Player->sendToastNotification(string $title, string $body) : void` - makes a grey box appear at the top of the player's screen containing the specified message
|
||||
|
||||
### `pocketmine\utils`
|
||||
- The following new API methods have been added:
|
||||
- `public static TextFormat::addBase(string $baseFormat, string $string) : string` - used for coloured log messages, changes the base formatting of a string by inserting the given formatting codes after every RESET code
|
||||
|
||||
## Internals
|
||||
- Improved performance of `ContainerTrait` dropping items on block destroy. (24e72ec109c1442b09558df89b6833cf2f2e0ec7)
|
||||
- Avoid repeated calls to `Position->getWorld()` (use local variables). (2940547026db40ce76deb46e992870de3ead79ad)
|
||||
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. (e90abecf38d9c57635fa0497514bba7e546a2469)
|
||||
- Console polling is now done on the main thread (no longer a performance concern).
|
||||
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie.
|
||||
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged.
|
||||
- `DelegateInventory` now uses `WeakReference` to track its inventory listener. This allows the delegate to be reused.
|
@ -41,7 +41,7 @@
|
||||
"pocketmine/classloader": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/locale-data": "~2.8.0 <2.8.9",
|
||||
"pocketmine/locale-data": "~2.9.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.4.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
@ -50,10 +50,11 @@
|
||||
"pocketmine/raklib-ipc": "^0.1.0",
|
||||
"pocketmine/snooze": "^0.3.0",
|
||||
"ramsey/uuid": "^4.1",
|
||||
"symfony/filesystem": "^5.4",
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.8.11",
|
||||
"phpstan/phpstan": "1.9.1",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
|
376
composer.lock
generated
376
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": "ed062ef1dc3113ad2a75ba4d4d5e174f",
|
||||
"content-hash": "b535dc262141936698680b92902f8e28",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -536,16 +536,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.8.7",
|
||||
"version": "2.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "e115d3d64a508065f1cedad1be55528906308456"
|
||||
"reference": "8813ffd2a4501521ca9433c534f3009f941de136"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/e115d3d64a508065f1cedad1be55528906308456",
|
||||
"reference": "e115d3d64a508065f1cedad1be55528906308456",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/8813ffd2a4501521ca9433c534f3009f941de136",
|
||||
"reference": "8813ffd2a4501521ca9433c534f3009f941de136",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -553,9 +553,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.8.7"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.9.2"
|
||||
},
|
||||
"time": "2022-08-21T20:37:16+00:00"
|
||||
"time": "2022-10-21T20:30:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -1022,6 +1022,318 @@
|
||||
],
|
||||
"time": "2022-09-16T03:22:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v5.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.8",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Filesystem\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v5.4.13"
|
||||
},
|
||||
"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": "2022-09-21T19:53:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
|
||||
},
|
||||
"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": "2022-05-24T11:49:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"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.26.0"
|
||||
},
|
||||
"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": "2022-05-24T11:49:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
|
||||
},
|
||||
"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": "2022-05-10T07:21:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.26.0",
|
||||
@ -1510,16 +1822,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.8.11",
|
||||
"version": "1.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "46e223dd68a620da18855c23046ddb00940b4014"
|
||||
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014",
|
||||
"reference": "46e223dd68a620da18855c23046ddb00940b4014",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f",
|
||||
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1549,7 +1861,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.8.11"
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1565,25 +1877,25 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-10-24T15:45:13+00:00"
|
||||
"time": "2022-11-04T13:35:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
||||
"reference": "4a3c437c09075736285d1cabb5c75bf27ed0bc84"
|
||||
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/4a3c437c09075736285d1cabb5c75bf27ed0bc84",
|
||||
"reference": "4a3c437c09075736285d1cabb5c75bf27ed0bc84",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/dea1f87344c6964c607d9076dee42d891f3923f0",
|
||||
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"phpstan/phpstan": "^1.5.0"
|
||||
"phpstan/phpstan": "^1.8.11"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<7.0"
|
||||
@ -1615,9 +1927,9 @@
|
||||
"description": "PHPUnit extensions and rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.1.1"
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.2.2"
|
||||
},
|
||||
"time": "2022-04-20T15:24:25+00:00"
|
||||
"time": "2022-10-28T10:23:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
@ -1669,16 +1981,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.17",
|
||||
"version": "9.2.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
|
||||
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
|
||||
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1734,7 +2046,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1742,7 +2054,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-30T12:24:04+00:00"
|
||||
"time": "2022-10-27T13:35:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@ -1987,16 +2299,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.5.25",
|
||||
"version": "9.5.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d"
|
||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
|
||||
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2069,7 +2381,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -2085,7 +2397,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-09-25T03:44:45+00:00"
|
||||
"time": "2022-10-28T06:00:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -168,6 +168,9 @@ timings:
|
||||
host: timings.pmmp.io
|
||||
|
||||
console:
|
||||
#Whether to accept commands via the console. If disabled, anything typed on the console will be ignored.
|
||||
#Useful to save resources on headless servers where the console is never used (e.g. hosted server, Docker, etc.)
|
||||
enable-input: true
|
||||
#Choose whether to enable server stats reporting on the console title.
|
||||
#NOTE: The title ticker will be disabled regardless if console colours are not enabled.
|
||||
title-tick: true
|
||||
|
@ -10,3 +10,4 @@ resource_stack:
|
||||
# - natural.zip
|
||||
# - vanilla.zip
|
||||
#If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here.
|
||||
#To specify a resource encryption key, put the key in the <resource>.key file alongside the resource pack. Example: vanilla.zip.key
|
||||
|
@ -30,7 +30,7 @@ use pocketmine\scheduler\GarbageCollectionTask;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Process;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function arsort;
|
||||
use function count;
|
||||
use function fclose;
|
||||
@ -398,7 +398,7 @@ class MemoryManager{
|
||||
|
||||
do{
|
||||
$continue = false;
|
||||
foreach($objects as $hash => $object){
|
||||
foreach(Utils::stringifyKeys($objects) as $hash => $object){
|
||||
if(!is_object($object)){
|
||||
continue;
|
||||
}
|
||||
@ -483,6 +483,11 @@ class MemoryManager{
|
||||
* @param object[] $objects reference parameter
|
||||
* @param int[] $refCounts reference parameter
|
||||
*
|
||||
* @phpstan-param array<string, object> $objects
|
||||
* @phpstan-param array<string, int> $refCounts
|
||||
* @phpstan-param-out array<string, object> $objects
|
||||
* @phpstan-param-out array<string, int> $refCounts
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function continueDump($from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||
|
@ -34,7 +34,7 @@ namespace pocketmine {
|
||||
use pocketmine\utils\Timezone;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
@ -201,6 +201,22 @@ JIT_WARNING
|
||||
ini_set('assert.exception', '1');
|
||||
}
|
||||
|
||||
function getopt_string(string $opt) : ?string{
|
||||
$opts = getopt("", ["$opt:"]);
|
||||
if(isset($opts[$opt])){
|
||||
if(is_string($opts[$opt])){
|
||||
return $opts[$opt];
|
||||
}
|
||||
if(is_array($opts[$opt])){
|
||||
critical_error("Cannot specify --$opt multiple times");
|
||||
}else{
|
||||
critical_error("Missing value for --$opt");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@ -252,16 +268,22 @@ JIT_WARNING
|
||||
|
||||
ErrorToExceptionHandler::set();
|
||||
|
||||
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
|
||||
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
|
||||
$dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR;
|
||||
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR;
|
||||
$dataPath = getopt_string("data") ?? $cwd;
|
||||
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
|
||||
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
||||
|
||||
if(!file_exists($dataPath)){
|
||||
mkdir($dataPath, 0777, true);
|
||||
if(!@mkdir($dataPath, 0777, true) && (!is_dir($dataPath) || !is_writable($dataPath))){
|
||||
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
|
||||
exit(1);
|
||||
}
|
||||
//this has to be done after we're sure the data path exists
|
||||
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
|
||||
if(!@mkdir($pluginPath, 0777, true) && (!is_dir($pluginPath) || !is_writable($pluginPath))){
|
||||
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
|
||||
exit(1);
|
||||
}
|
||||
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$lockFilePath = Path::join($dataPath, 'server.lock');
|
||||
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
|
||||
@ -273,6 +295,7 @@ JIT_WARNING
|
||||
//Logger has a dependency on timezone
|
||||
Timezone::init();
|
||||
|
||||
$opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
if(isset($opts["enable-ansi"])){
|
||||
Terminal::init(true);
|
||||
}elseif(isset($opts["disable-ansi"])){
|
||||
|
@ -31,7 +31,7 @@ use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\SimpleCommandMap;
|
||||
use pocketmine\console\ConsoleCommandSender;
|
||||
use pocketmine\console\ConsoleReaderThread;
|
||||
use pocketmine\console\ConsoleReaderChildProcessDaemon;
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
||||
use pocketmine\crash\CrashDump;
|
||||
@ -88,12 +88,12 @@ use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\snooze\SleeperHandler;
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\stats\SendUsageTask;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\updater\UpdateChecker;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Internet;
|
||||
@ -115,7 +115,7 @@ use pocketmine\world\World;
|
||||
use pocketmine\world\WorldCreationOptions;
|
||||
use pocketmine\world\WorldManager;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_sum;
|
||||
use function base64_encode;
|
||||
use function cli_set_process_title;
|
||||
@ -166,7 +166,6 @@ use function zlib_encode;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const PHP_EOL;
|
||||
use const PHP_INT_MAX;
|
||||
use const PTHREADS_INHERIT_NONE;
|
||||
use const ZLIB_ENCODING_GZIP;
|
||||
|
||||
/**
|
||||
@ -226,7 +225,8 @@ class Server{
|
||||
|
||||
private MemoryManager $memoryManager;
|
||||
|
||||
private ConsoleReaderThread $console;
|
||||
private ?ConsoleReaderChildProcessDaemon $console = null;
|
||||
private ?ConsoleCommandSender $consoleSender = null;
|
||||
|
||||
private SimpleCommandMap $commandMap;
|
||||
|
||||
@ -609,6 +609,10 @@ class Server{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method's results are unpredictable. The string "Steve" will return the player named "SteveJobs",
|
||||
* until another player named "SteveJ" joins the server, at which point it will return that player instead. Prefer
|
||||
* filtering the results of {@link Server::getOnlinePlayers()} yourself.
|
||||
*
|
||||
* Returns an online player whose name begins with or equals the given string (case insensitive).
|
||||
* The closest match will be returned, or null if there are no online matches.
|
||||
*
|
||||
@ -1045,22 +1049,14 @@ class Server{
|
||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
||||
|
||||
//TODO: move console parts to a separate component
|
||||
$consoleSender = new ConsoleCommandSender($this, $this->language);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $consoleSender);
|
||||
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
|
||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder);
|
||||
|
||||
$consoleNotifier = new SleeperNotifier();
|
||||
$commandBuffer = new \Threaded();
|
||||
$this->console = new ConsoleReaderThread($commandBuffer, $consoleNotifier);
|
||||
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($commandBuffer, $consoleSender) : void{
|
||||
Timings::$serverCommand->startTiming();
|
||||
while(($line = $commandBuffer->shift()) !== null){
|
||||
$this->dispatchCommand($consoleSender, (string) $line);
|
||||
}
|
||||
Timings::$serverCommand->stopTiming();
|
||||
});
|
||||
$this->console->start(PTHREADS_INHERIT_NONE);
|
||||
//TODO: move console parts to a separate component
|
||||
if($this->configGroup->getPropertyBool("console.enable-input", true)){
|
||||
$this->console = new ConsoleReaderChildProcessDaemon($this->logger);
|
||||
}
|
||||
|
||||
$this->tickProcessor();
|
||||
$this->forceShutdown();
|
||||
@ -1513,7 +1509,7 @@ class Server{
|
||||
$this->configGroup->save();
|
||||
}
|
||||
|
||||
if(isset($this->console)){
|
||||
if($this->console !== null){
|
||||
$this->getLogger()->debug("Closing console");
|
||||
$this->console->quit();
|
||||
}
|
||||
@ -1720,7 +1716,7 @@ class Server{
|
||||
$session = $player->getNetworkSession();
|
||||
$position = $player->getPosition();
|
||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
|
||||
TextFormat::AQUA . $player->getName() . TextFormat::WHITE,
|
||||
TextFormat::AQUA . $player->getName() . TextFormat::RESET,
|
||||
$session->getIp(),
|
||||
(string) $session->getPort(),
|
||||
(string) $player->getId(),
|
||||
@ -1857,6 +1853,15 @@ class Server{
|
||||
|
||||
$this->getMemoryManager()->check();
|
||||
|
||||
if($this->console !== null){
|
||||
Timings::$serverCommand->startTiming();
|
||||
while(($line = $this->console->readLine()) !== null){
|
||||
$this->consoleSender ??= new ConsoleCommandSender($this, $this->language);
|
||||
$this->dispatchCommand($this->consoleSender, $line);
|
||||
}
|
||||
Timings::$serverCommand->stopTiming();
|
||||
}
|
||||
|
||||
Timings::$serverTick->stopTiming();
|
||||
|
||||
$now = microtime(true);
|
||||
|
@ -31,9 +31,9 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.10.0";
|
||||
public const BASE_VERSION = "4.11.0-BETA1";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
|
||||
private function __construct(){
|
||||
//NOOP
|
||||
|
@ -164,9 +164,10 @@ class Bamboo extends Transparent{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$below = $this->position->getWorld()->getBlock($this->position->down());
|
||||
$world = $this->position->getWorld();
|
||||
$below = $world->getBlock($this->position->down());
|
||||
if(!$this->canBeSupportedBy($below) && !$below->isSameType($this)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +213,7 @@ class Bamboo extends Transparent{
|
||||
}
|
||||
}
|
||||
|
||||
$tx = new BlockTransaction($this->position->getWorld());
|
||||
$tx = new BlockTransaction($world);
|
||||
foreach($newBlocks as $idx => $newBlock){
|
||||
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
|
||||
}
|
||||
|
@ -82,8 +82,9 @@ final class BambooSapling extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canBeSupportedBy($this->position->getWorld()->getBlock($this->position->down()))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world = $this->position->getWorld();
|
||||
if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
|
||||
$world->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,7 @@ abstract class BaseRail extends Flowable{
|
||||
$thisConnections = $this->getConnectedDirections();
|
||||
$changed = false;
|
||||
|
||||
$world = $this->position->getWorld();
|
||||
do{
|
||||
$possible = $this->getPossibleConnectionDirections($thisConnections);
|
||||
$continue = false;
|
||||
@ -189,7 +190,7 @@ abstract class BaseRail extends Flowable{
|
||||
if(isset($otherPossible[$otherSide])){
|
||||
$otherConnections[] = $otherSide;
|
||||
$other->setConnections($otherConnections);
|
||||
$this->position->getWorld()->setBlock($other->position, $other);
|
||||
$world->setBlock($other->position, $other);
|
||||
|
||||
$changed = true;
|
||||
$thisConnections[] = $thisSide;
|
||||
@ -202,7 +203,7 @@ abstract class BaseRail extends Flowable{
|
||||
|
||||
if($changed){
|
||||
$this->setConnections($thisConnections);
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,12 +221,13 @@ abstract class BaseRail extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$world = $this->position->getWorld();
|
||||
if(!$this->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
foreach($this->getCurrentShapeConnections() as $connection){
|
||||
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -179,10 +179,11 @@ final class Bell extends Transparent{
|
||||
}
|
||||
|
||||
public function ring(int $faceHit) : void{
|
||||
$this->position->getWorld()->addSound($this->position, new BellRingSound());
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
$world = $this->position->getWorld();
|
||||
$world->addSound($this->position, new BellRingSound());
|
||||
$tile = $world->getTile($this->position);
|
||||
if($tile instanceof TileBell){
|
||||
$this->position->getWorld()->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
|
||||
$world->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,10 +179,11 @@ class Block{
|
||||
* Note: Do not call this directly. Pass the block to {@link World::setBlock()} instead.
|
||||
*/
|
||||
public function writeStateToWorld() : void{
|
||||
$this->position->getWorld()->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
|
||||
$world = $this->position->getWorld();
|
||||
$world->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
|
||||
|
||||
$tileType = $this->idInfo->getTileClass();
|
||||
$oldTile = $this->position->getWorld()->getTile($this->position);
|
||||
$oldTile = $world->getTile($this->position);
|
||||
if($oldTile !== null){
|
||||
if($tileType === null || !($oldTile instanceof $tileType)){
|
||||
$oldTile->close();
|
||||
@ -196,8 +197,8 @@ class Block{
|
||||
* @var Tile $tile
|
||||
* @see Tile::__construct()
|
||||
*/
|
||||
$tile = new $tileType($this->position->getWorld(), $this->position->asVector3());
|
||||
$this->position->getWorld()->addTile($tile);
|
||||
$tile = new $tileType($world, $this->position->asVector3());
|
||||
$world->addTile($tile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,10 +279,11 @@ class Block{
|
||||
* Do the actions needed so the block is broken with the Item
|
||||
*/
|
||||
public function onBreak(Item $item, ?Player $player = null) : bool{
|
||||
if(($t = $this->position->getWorld()->getTile($this->position)) !== null){
|
||||
$world = $this->position->getWorld();
|
||||
if(($t = $world->getTile($this->position)) !== null){
|
||||
$t->onBlockDestroyed();
|
||||
}
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -130,10 +130,11 @@ class BrewingStand extends Transparent{
|
||||
}
|
||||
|
||||
public function onScheduledUpdate() : void{
|
||||
$brewing = $this->position->getWorld()->getTile($this->position);
|
||||
$world = $this->position->getWorld();
|
||||
$brewing = $world->getTile($this->position);
|
||||
if($brewing instanceof TileBrewingStand){
|
||||
if($brewing->onUpdate()){
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
|
||||
$world->scheduleDelayedBlockUpdate($this->position, 1);
|
||||
}
|
||||
|
||||
$changed = false;
|
||||
@ -146,7 +147,7 @@ class BrewingStand extends Transparent{
|
||||
}
|
||||
|
||||
if($changed){
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +73,10 @@ abstract class Button extends Flowable{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->pressed){
|
||||
$this->pressed = true;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -84,8 +85,9 @@ abstract class Button extends Flowable{
|
||||
public function onScheduledUpdate() : void{
|
||||
if($this->pressed){
|
||||
$this->pressed = false;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,13 +88,14 @@ class Cactus extends Transparent{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
$world = $this->position->getWorld();
|
||||
if($down->getId() !== BlockLegacyIds::SAND && !$down->isSameType($this)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
foreach(Facing::HORIZONTAL as $side){
|
||||
$b = $this->getSide($side);
|
||||
if($b->isSolid()){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -107,28 +108,29 @@ class Cactus extends Transparent{
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
||||
$world = $this->position->getWorld();
|
||||
if($this->age === self::MAX_AGE){
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
||||
if(!$world->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
||||
break;
|
||||
}
|
||||
$b = $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
||||
$b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
||||
if($b->getId() === BlockLegacyIds::AIR){
|
||||
$ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
|
||||
$world->setBlock($b->position, $ev->getNewState());
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->age = 0;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}else{
|
||||
++$this->age;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ class Chest extends Transparent{
|
||||
}
|
||||
|
||||
public function onPostPlace() : void{
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
$world = $this->position->getWorld();
|
||||
$tile = $world->getTile($this->position);
|
||||
if($tile instanceof TileChest){
|
||||
foreach([false, true] as $clockwise){
|
||||
$side = Facing::rotateY($this->facing, $clockwise);
|
||||
$c = $this->getSide($side);
|
||||
if($c instanceof Chest && $c->isSameType($this) && $c->facing === $this->facing){
|
||||
$world = $this->position->getWorld();
|
||||
$pair = $world->getTile($c->position);
|
||||
if($pair instanceof TileChest && !$pair->isPaired()){
|
||||
[$left, $right] = $clockwise ? [$c, $this] : [$this, $c];
|
||||
|
@ -100,21 +100,23 @@ class DaylightSensor extends Transparent{
|
||||
}
|
||||
|
||||
public function onScheduledUpdate() : void{
|
||||
$world = $this->position->getWorld();
|
||||
$signalStrength = $this->recalculateSignalStrength();
|
||||
if($this->signalStrength !== $signalStrength){
|
||||
$this->signalStrength = $signalStrength;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 20);
|
||||
$world->scheduleDelayedBlockUpdate($this->position, 20);
|
||||
}
|
||||
|
||||
private function recalculateSignalStrength() : int{
|
||||
$lightLevel = $this->position->getWorld()->getRealBlockSkyLightAt($this->position->x, $this->position->y, $this->position->z);
|
||||
$world = $this->position->getWorld();
|
||||
$lightLevel = $world->getRealBlockSkyLightAt($this->position->x, $this->position->y, $this->position->z);
|
||||
if($this->inverted){
|
||||
return 15 - $lightLevel;
|
||||
}
|
||||
|
||||
$sunAngle = $this->position->getWorld()->getSunAnglePercentage();
|
||||
$sunAngle = $world->getSunAnglePercentage();
|
||||
return max(0, (int) round($lightLevel * cos(($sunAngle + ((($sunAngle < 0.5 ? 0 : 1) - $sunAngle) / 5)) * 2 * M_PI)));
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ use function mt_rand;
|
||||
class DeadBush extends Flowable{
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->getSide(Facing::DOWN)->isTransparent()){
|
||||
if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ class DeadBush extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Facing::DOWN)->isTransparent()){
|
||||
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
@ -64,4 +64,14 @@ class DeadBush extends Flowable{
|
||||
public function getFlammability() : int{
|
||||
return 100;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
$blockId = $block->getId();
|
||||
return $blockId === BlockLegacyIds::SAND
|
||||
|| $blockId === BlockLegacyIds::PODZOL
|
||||
|| $blockId === BlockLegacyIds::MYCELIUM
|
||||
|| $blockId === BlockLegacyIds::DIRT
|
||||
|| $blockId === BlockLegacyIds::HARDENED_CLAY
|
||||
|| $blockId === BlockLegacyIds::STAINED_HARDENED_CLAY;
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,9 @@ class Dirt extends Opaque{
|
||||
$item->applyDamage(1);
|
||||
|
||||
$newBlock = $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND();
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
||||
$world = $this->position->getWorld();
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$world->setBlock($this->position, $newBlock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -163,13 +163,14 @@ class Door extends Transparent{
|
||||
$this->open = !$this->open;
|
||||
|
||||
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
|
||||
$world = $this->position->getWorld();
|
||||
if($other instanceof Door && $other->isSameType($this)){
|
||||
$other->open = $this->open;
|
||||
$this->position->getWorld()->setBlock($other->position, $other);
|
||||
$world->setBlock($other->position, $other);
|
||||
}
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->addSound($this->position, new DoorSound());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -62,8 +62,9 @@ class DragonEgg extends Transparent implements Fallable{
|
||||
}
|
||||
|
||||
public function teleport() : void{
|
||||
$world = $this->position->getWorld();
|
||||
for($tries = 0; $tries < 16; ++$tries){
|
||||
$block = $this->position->getWorld()->getBlockAt(
|
||||
$block = $world->getBlockAt(
|
||||
$this->position->x + mt_rand(-16, 16),
|
||||
max(World::Y_MIN, min(World::Y_MAX - 1, $this->position->y + mt_rand(-8, 8))),
|
||||
$this->position->z + mt_rand(-16, 16)
|
||||
@ -76,9 +77,9 @@ class DragonEgg extends Transparent implements Fallable{
|
||||
}
|
||||
|
||||
$blockPos = $ev->getTo();
|
||||
$this->position->getWorld()->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||
$this->position->getWorld()->setBlock($blockPos, $this);
|
||||
$world->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
|
||||
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||
$world->setBlock($blockPos, $this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -78,16 +78,17 @@ class Farmland extends Transparent{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
$world = $this->position->getWorld();
|
||||
if(!$this->canHydrate()){
|
||||
if($this->wetness > 0){
|
||||
$this->wetness--;
|
||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
||||
$world->setBlock($this->position, $this, false);
|
||||
}else{
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
|
||||
$world->setBlock($this->position, VanillaBlocks::DIRT());
|
||||
}
|
||||
}elseif($this->wetness < self::MAX_WETNESS){
|
||||
$this->wetness = self::MAX_WETNESS;
|
||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
||||
$world->setBlock($this->position, $this, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,8 +117,9 @@ class FenceGate extends Transparent{
|
||||
}
|
||||
}
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->addSound($this->position, new DoorSound());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -100,10 +100,11 @@ class Fire extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$world = $this->position->getWorld();
|
||||
if($this->getSide(Facing::DOWN)->isTransparent() && !$this->hasAdjacentFlammableBlocks()){
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||
}else{
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,11 +137,12 @@ class Fire extends Flowable{
|
||||
}
|
||||
}
|
||||
|
||||
$world = $this->position->getWorld();
|
||||
if($result !== null){
|
||||
$this->position->getWorld()->setBlock($this->position, $result);
|
||||
$world->setBlock($this->position, $result);
|
||||
}
|
||||
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||
|
||||
if($canSpread){
|
||||
$this->burnBlocksAround();
|
||||
@ -181,7 +183,8 @@ class Fire extends Flowable{
|
||||
if(!$ev->isCancelled()){
|
||||
$block->onIncinerate();
|
||||
|
||||
if($this->position->getWorld()->getBlock($block->getPosition())->isSameState($block)){
|
||||
$world = $this->position->getWorld();
|
||||
if($world->getBlock($block->getPosition())->isSameState($block)){
|
||||
$spreadedFire = false;
|
||||
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
|
||||
$fire = clone $this;
|
||||
@ -189,7 +192,7 @@ class Fire extends Flowable{
|
||||
$spreadedFire = $this->spreadBlock($block, $fire);
|
||||
}
|
||||
if(!$spreadedFire){
|
||||
$this->position->getWorld()->setBlock($block->position, VanillaBlocks::AIR());
|
||||
$world->setBlock($block->position, VanillaBlocks::AIR());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ class FlowerPot extends Flowable{
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$world = $this->position->getWorld();
|
||||
$plant = $item->getBlock();
|
||||
if($this->plant !== null){
|
||||
if($this->isValidPlant($plant)){
|
||||
@ -137,16 +138,16 @@ class FlowerPot extends Flowable{
|
||||
$removedItems = $player->getInventory()->addItem(...$removedItems);
|
||||
}
|
||||
foreach($removedItems as $drops){
|
||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), $drops);
|
||||
$world->dropItem($this->position->add(0.5, 0.5, 0.5), $drops);
|
||||
}
|
||||
|
||||
$this->setPlant(null);
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
return true;
|
||||
}elseif($this->isValidPlant($plant)){
|
||||
$this->setPlant($plant);
|
||||
$item->pop();
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -56,16 +56,18 @@ class FrostedIce extends Ice{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$world = $this->position->getWorld();
|
||||
if(!$this->checkAdjacentBlocks(2)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
}
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
$world = $this->position->getWorld();
|
||||
if((!$this->checkAdjacentBlocks(4) || mt_rand(0, 2) === 0) &&
|
||||
$this->position->getWorld()->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){
|
||||
$world->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){
|
||||
if($this->tryMelt()){
|
||||
foreach($this->getAllSides() as $block){
|
||||
if($block instanceof FrostedIce){
|
||||
@ -74,7 +76,7 @@ class FrostedIce extends Ice{
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,18 +108,19 @@ class FrostedIce extends Ice{
|
||||
* @return bool Whether the ice was destroyed.
|
||||
*/
|
||||
private function tryMelt() : bool{
|
||||
$world = $this->position->getWorld();
|
||||
if($this->age >= self::MAX_AGE){
|
||||
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->age++;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -83,12 +83,13 @@ class Furnace extends Opaque{
|
||||
}
|
||||
|
||||
public function onScheduledUpdate() : void{
|
||||
$furnace = $this->position->getWorld()->getTile($this->position);
|
||||
$world = $this->position->getWorld();
|
||||
$furnace = $world->getTile($this->position);
|
||||
if($furnace instanceof TileFurnace && $furnace->onUpdate()){
|
||||
if(mt_rand(1, 60) === 1){ //in vanilla this is between 1 and 5 seconds; try to average about 3
|
||||
$this->position->getWorld()->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
|
||||
$world->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
|
||||
}
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
|
||||
$world->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,13 +53,14 @@ class Grass extends Opaque{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
$lightAbove = $this->position->getWorld()->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
|
||||
if($lightAbove < 4 && $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
|
||||
$world = $this->position->getWorld();
|
||||
$lightAbove = $world->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
|
||||
if($lightAbove < 4 && $world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
|
||||
//grass dies
|
||||
$ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState(), false);
|
||||
$world->setBlock($this->position, $ev->getNewState(), false);
|
||||
}
|
||||
}elseif($lightAbove >= 9){
|
||||
//try grass spread
|
||||
@ -68,12 +69,12 @@ class Grass extends Opaque{
|
||||
$y = mt_rand($this->position->y - 3, $this->position->y + 1);
|
||||
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
||||
|
||||
$b = $this->position->getWorld()->getBlockAt($x, $y, $z);
|
||||
$b = $world->getBlockAt($x, $y, $z);
|
||||
if(
|
||||
!($b instanceof Dirt) ||
|
||||
$b->isCoarse() ||
|
||||
$this->position->getWorld()->getFullLightAt($x, $y + 1, $z) < 4 ||
|
||||
$this->position->getWorld()->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
|
||||
$world->getFullLightAt($x, $y + 1, $z) < 4 ||
|
||||
$world->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
|
||||
){
|
||||
continue;
|
||||
}
|
||||
@ -81,7 +82,7 @@ class Grass extends Opaque{
|
||||
$ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState(), false);
|
||||
$world->setBlock($b->position, $ev->getNewState(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,23 +92,24 @@ class Grass extends Opaque{
|
||||
if($face !== Facing::UP){
|
||||
return false;
|
||||
}
|
||||
$world = $this->position->getWorld();
|
||||
if($item instanceof Fertilizer){
|
||||
$item->pop();
|
||||
TallGrassObject::growGrass($this->position->getWorld(), $this->position, new Random(mt_rand()), 8, 2);
|
||||
TallGrassObject::growGrass($world, $this->position, new Random(mt_rand()), 8, 2);
|
||||
|
||||
return true;
|
||||
}elseif($item instanceof Hoe){
|
||||
$item->applyDamage(1);
|
||||
$newBlock = VanillaBlocks::FARMLAND();
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$world->setBlock($this->position, $newBlock);
|
||||
|
||||
return true;
|
||||
}elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getId() === BlockLegacyIds::AIR){
|
||||
$item->applyDamage(1);
|
||||
$newBlock = VanillaBlocks::GRASS_PATH();
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||
$world->setBlock($this->position, $newBlock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -51,11 +51,12 @@ class Ice extends Transparent{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if($this->position->getWorld()->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||
$world = $this->position->getWorld();
|
||||
if($world->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,11 +151,12 @@ class ItemFrame extends Flowable{
|
||||
if($this->framedItem === null){
|
||||
return false;
|
||||
}
|
||||
$world = $this->position->getWorld();
|
||||
if(lcg_value() <= $this->itemDropChance){
|
||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
||||
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
||||
}
|
||||
$this->setFramedItem(null);
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,7 @@ class Leaves extends Transparent{
|
||||
/**
|
||||
* @param true[] $visited reference parameter
|
||||
* @phpstan-param array<int, true> $visited
|
||||
* @phpstan-param-out array<int, true> $visited
|
||||
*/
|
||||
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
|
||||
$index = World::blockHash($pos->x, $pos->y, $pos->z);
|
||||
@ -123,11 +124,12 @@ class Leaves extends Transparent{
|
||||
if(!$this->noDecay && $this->checkDecay){
|
||||
$ev = new LeavesDecayEvent($this);
|
||||
$ev->call();
|
||||
$world = $this->position->getWorld();
|
||||
if($ev->isCancelled() || $this->findLog($this->position)){
|
||||
$this->checkDecay = false;
|
||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
||||
$world->setBlock($this->position, $this, false);
|
||||
}else{
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +128,9 @@ class Lectern extends Transparent{
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->book === null && $item instanceof WritableBookBase){
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setBook($item));
|
||||
$this->position->getWorld()->addSound($this->position, new LecternPlaceBookSound());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this->setBook($item));
|
||||
$world->addSound($this->position, new LecternPlaceBookSound());
|
||||
$item->pop();
|
||||
}
|
||||
return true;
|
||||
@ -137,8 +138,9 @@ class Lectern extends Transparent{
|
||||
|
||||
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
|
||||
if($this->book !== null){
|
||||
$this->position->getWorld()->dropItem($this->position->up(), $this->book);
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setBook(null));
|
||||
$world = $this->position->getWorld();
|
||||
$world->dropItem($this->position->up(), $this->book);
|
||||
$world->setBlock($this->position, $this->setBook(null));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -152,12 +154,13 @@ class Lectern extends Transparent{
|
||||
}
|
||||
|
||||
$this->viewedPage = $newPage;
|
||||
$world = $this->position->getWorld();
|
||||
if(!$this->producingSignal){
|
||||
$this->producingSignal = true;
|
||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
|
||||
$world->scheduleDelayedBlockUpdate($this->position, 1);
|
||||
}
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -127,8 +127,9 @@ class Lever extends Flowable{
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$this->activated = !$this->activated;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->addSound(
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->addSound(
|
||||
$this->position->add(0.5, 0.5, 0.5),
|
||||
$this->activated ? new RedstonePowerOnSound() : new RedstonePowerOffSound()
|
||||
);
|
||||
|
@ -329,7 +329,7 @@ abstract class Liquid extends Transparent{
|
||||
}
|
||||
|
||||
if($adjacentDecay <= self::MAX_DECAY){
|
||||
$calculator = new MinimumCostFlowCalculator($this->position->getWorld(), $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
|
||||
$calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
|
||||
foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
|
||||
$this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);
|
||||
}
|
||||
@ -348,11 +348,12 @@ abstract class Liquid extends Transparent{
|
||||
$ev = new BlockSpreadEvent($block, $this, $new);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$world = $this->position->getWorld();
|
||||
if($block->getId() !== BlockLegacyIds::AIR){
|
||||
$this->position->getWorld()->useBreakOn($block->position);
|
||||
$world->useBreakOn($block->position);
|
||||
}
|
||||
|
||||
$this->position->getWorld()->setBlock($block->position, $ev->getNewState());
|
||||
$world->setBlock($block->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -382,8 +383,9 @@ abstract class Liquid extends Transparent{
|
||||
$ev = new BlockFormEvent($this, $result);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -49,13 +49,14 @@ class Mycelium extends Opaque{
|
||||
$x = mt_rand($this->position->x - 1, $this->position->x + 1);
|
||||
$y = mt_rand($this->position->y - 2, $this->position->y + 2);
|
||||
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
||||
$block = $this->position->getWorld()->getBlockAt($x, $y, $z);
|
||||
$world = $this->position->getWorld();
|
||||
$block = $world->getBlockAt($x, $y, $z);
|
||||
if($block instanceof Dirt && !$block->isCoarse()){
|
||||
if($block->getSide(Facing::UP) instanceof Transparent){
|
||||
$ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($block->position, $ev->getNewState());
|
||||
$world->setBlock($block->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,9 @@ class Pumpkin extends Opaque{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($item instanceof Shears && in_array($face, Facing::HORIZONTAL, true)){
|
||||
$item->applyDamage(1);
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
|
||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
|
||||
$world->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -96,12 +96,13 @@ class Sapling extends Flowable{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if($this->position->getWorld()->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 && mt_rand(1, 7) === 1){
|
||||
$world = $this->position->getWorld();
|
||||
if($world->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 && mt_rand(1, 7) === 1){
|
||||
if($this->ready){
|
||||
$this->grow(null);
|
||||
}else{
|
||||
$this->ready = true;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,11 +111,12 @@ class SnowLayer extends Flowable implements Fallable{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if($this->position->getWorld()->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||
$world = $this->position->getWorld();
|
||||
if($world->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||
$ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,14 @@ abstract class Stem extends Crops{
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if(mt_rand(0, 2) === 1){
|
||||
$world = $this->position->getWorld();
|
||||
if($this->age < self::MAX_AGE){
|
||||
$block = clone $this;
|
||||
++$block->age;
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}else{
|
||||
$grow = $this->getPlant();
|
||||
@ -57,7 +58,7 @@ abstract class Stem extends Crops{
|
||||
$ev = new BlockGrowEvent($side, $grow);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($side->position, $ev->getNewState());
|
||||
$world->setBlock($side->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
class Sugarcane extends Flowable{
|
||||
public const MAX_AGE = 15;
|
||||
@ -49,27 +50,37 @@ class Sugarcane extends Flowable{
|
||||
return 0b1111;
|
||||
}
|
||||
|
||||
private function grow() : bool{
|
||||
private function seekToBottom() : Position{
|
||||
$world = $this->position->getWorld();
|
||||
$bottom = $this->position;
|
||||
while(($next = $world->getBlock($bottom->down()))->isSameType($this)){
|
||||
$bottom = $next->position;
|
||||
}
|
||||
return $bottom;
|
||||
}
|
||||
|
||||
private function grow(Position $pos) : bool{
|
||||
$grew = false;
|
||||
$world = $pos->getWorld();
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
||||
if(!$world->isInWorld($pos->x, $pos->y + $y, $pos->z)){
|
||||
break;
|
||||
}
|
||||
$b = $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
||||
$b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z);
|
||||
if($b->getId() === BlockLegacyIds::AIR){
|
||||
$ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE());
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
|
||||
$world->setBlock($b->position, $ev->getNewState());
|
||||
$grew = true;
|
||||
}else{
|
||||
}elseif(!$b->isSameType($this)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->age = 0;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($pos, $this);
|
||||
return $grew;
|
||||
}
|
||||
|
||||
@ -86,7 +97,7 @@ class Sugarcane extends Flowable{
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($item instanceof Fertilizer){
|
||||
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
|
||||
if($this->grow($this->seekToBottom())){
|
||||
$item->pop();
|
||||
}
|
||||
|
||||
@ -110,7 +121,7 @@ class Sugarcane extends Flowable{
|
||||
public function onRandomTick() : void{
|
||||
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
||||
if($this->age === self::MAX_AGE){
|
||||
$this->grow();
|
||||
$this->grow($this->position);
|
||||
}else{
|
||||
++$this->age;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
|
@ -90,6 +90,7 @@ class SweetBerryBush extends Flowable{
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$world = $this->position->getWorld();
|
||||
if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){
|
||||
$block = clone $this;
|
||||
$block->age++;
|
||||
@ -98,13 +99,13 @@ class SweetBerryBush extends Flowable{
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
$item->pop();
|
||||
}
|
||||
|
||||
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
|
||||
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));
|
||||
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
|
||||
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -110,11 +110,12 @@ class TNT extends Opaque{
|
||||
}
|
||||
|
||||
public function ignite(int $fuse = 80) : void{
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||
|
||||
$mot = (new Random())->nextSignedFloat() * M_PI * 2;
|
||||
|
||||
$tnt = new PrimedTNT(Location::fromObject($this->position->add(0.5, 0, 0.5), $this->position->getWorld()));
|
||||
$tnt = new PrimedTNT(Location::fromObject($this->position->add(0.5, 0, 0.5), $world));
|
||||
$tnt->setFuse($fuse);
|
||||
$tnt->setWorksUnderwater($this->worksUnderwater);
|
||||
$tnt->setMotion(new Vector3(-sin($mot) * 0.02, 0.2, -cos($mot) * 0.02));
|
||||
|
@ -96,8 +96,9 @@ class Trapdoor extends Transparent{
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$this->open = !$this->open;
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
||||
$world = $this->position->getWorld();
|
||||
$world->setBlock($this->position, $this);
|
||||
$world->addSound($this->position, new DoorSound());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -141,10 +141,11 @@ class Vine extends Flowable{
|
||||
}
|
||||
|
||||
if($changed){
|
||||
$world = $this->position->getWorld();
|
||||
if(count($this->faces) === 0){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ class BarrelInventory extends SimpleInventory implements BlockInventory{
|
||||
|
||||
protected function animateBlock(bool $isOpen) : void{
|
||||
$holder = $this->getHolder();
|
||||
$block = $holder->getWorld()->getBlock($holder);
|
||||
$world = $holder->getWorld();
|
||||
$block = $world->getBlock($holder);
|
||||
if($block instanceof Barrel){
|
||||
$holder->getWorld()->setBlock($holder, $block->setOpen($isOpen));
|
||||
$world->setBlock($holder, $block->setOpen($isOpen));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +96,10 @@ trait ContainerTrait{
|
||||
$inv = $this->getRealInventory();
|
||||
$pos = $this->getPosition();
|
||||
|
||||
$world = $pos->getWorld();
|
||||
$dropPos = $pos->add(0.5, 0.5, 0.5);
|
||||
foreach($inv->getContents() as $k => $item){
|
||||
$pos->getWorld()->dropItem($pos->add(0.5, 0.5, 0.5), $item);
|
||||
$world->dropItem($dropPos, $item);
|
||||
}
|
||||
$inv->clearAll();
|
||||
}
|
||||
|
@ -46,14 +46,15 @@ trait FallableTrait{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$pos = $this->getPosition();
|
||||
$down = $pos->getWorld()->getBlock($pos->getSide(Facing::DOWN));
|
||||
$world = $pos->getWorld();
|
||||
$down = $world->getBlock($pos->getSide(Facing::DOWN));
|
||||
if($down->canBeReplaced()){
|
||||
$pos->getWorld()->setBlock($pos, VanillaBlocks::AIR());
|
||||
$world->setBlock($pos, VanillaBlocks::AIR());
|
||||
|
||||
$block = $this;
|
||||
if(!($block instanceof Block)) throw new AssumptionFailedError(__TRAIT__ . " should only be used by Blocks");
|
||||
|
||||
$fall = new FallingBlock(Location::fromObject($pos->add(0.5, 0, 0.5), $pos->getWorld()), $block);
|
||||
$fall = new FallingBlock(Location::fromObject($pos->add(0.5, 0, 0.5), $world), $block);
|
||||
$fall->spawnToAll();
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,13 @@ declare(strict_types=1);
|
||||
namespace pocketmine\command;
|
||||
|
||||
use pocketmine\command\utils\CommandException;
|
||||
use pocketmine\console\ConsoleCommandSender;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function explode;
|
||||
use function str_replace;
|
||||
@ -227,12 +227,12 @@ abstract class Command{
|
||||
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
||||
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
|
||||
if($sendToSource && !($source instanceof ConsoleCommandSender)){
|
||||
if($sendToSource){
|
||||
$source->sendMessage($message);
|
||||
}
|
||||
|
||||
foreach($users as $user){
|
||||
if($user instanceof ConsoleCommandSender){
|
||||
if($user instanceof BroadcastLoggerForwarder){
|
||||
$user->sendMessage($result);
|
||||
}elseif($user !== $source){
|
||||
$user->sendMessage($colored);
|
||||
|
@ -28,9 +28,9 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
@ -62,7 +62,20 @@ class FormattedCommandAlias extends Command{
|
||||
foreach($this->formatStrings as $formatString){
|
||||
try{
|
||||
$formatArgs = CommandStringHelper::parseQuoteAware($formatString);
|
||||
$commands[] = array_map(fn(string $formatArg) => $this->buildCommand($formatArg, $args), $formatArgs);
|
||||
$unresolved = [];
|
||||
$processedArgs = [];
|
||||
foreach($formatArgs as $formatArg){
|
||||
$processedArg = $this->buildCommand($formatArg, $args);
|
||||
if($processedArg === null){
|
||||
$unresolved[] = $formatArg;
|
||||
}elseif(count($unresolved) !== 0){
|
||||
//unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
|
||||
throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
|
||||
}else{
|
||||
$processedArgs[] = $processedArg;
|
||||
}
|
||||
}
|
||||
$commands[] = $processedArgs;
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$sender->sendMessage(TextFormat::RED . $e->getMessage());
|
||||
return false;
|
||||
@ -107,7 +120,7 @@ class FormattedCommandAlias extends Command{
|
||||
/**
|
||||
* @param string[] $args
|
||||
*/
|
||||
private function buildCommand(string $formatString, array $args) : string{
|
||||
private function buildCommand(string $formatString, array $args) : ?string{
|
||||
$index = 0;
|
||||
while(($index = strpos($formatString, '$', $index)) !== false){
|
||||
$start = $index;
|
||||
@ -129,6 +142,9 @@ class FormattedCommandAlias extends Command{
|
||||
}
|
||||
|
||||
$replacement = self::buildReplacement($args, $position, $rest);
|
||||
if($replacement === null){
|
||||
return null;
|
||||
}
|
||||
|
||||
$end = $index + strlen($fullPlaceholder);
|
||||
$formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
|
||||
@ -143,9 +159,9 @@ class FormattedCommandAlias extends Command{
|
||||
* @param string[] $args
|
||||
* @phpstan-param list<string> $args
|
||||
*/
|
||||
private static function buildReplacement(array $args, int $position, bool $rest) : string{
|
||||
$replacement = "";
|
||||
private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
|
||||
if($rest && $position < count($args)){
|
||||
$replacement = "";
|
||||
for($i = $position, $c = count($args); $i < $c; ++$i){
|
||||
if($i !== $position){
|
||||
$replacement .= " ";
|
||||
@ -153,11 +169,13 @@ class FormattedCommandAlias extends Command{
|
||||
|
||||
$replacement .= $args[$i];
|
||||
}
|
||||
|
||||
return $replacement;
|
||||
}elseif($position < count($args)){
|
||||
$replacement .= $args[$position];
|
||||
return $args[$position];
|
||||
}
|
||||
|
||||
return $replacement;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,10 +272,11 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
//These registered commands have absolute priority
|
||||
$lowerAlias = strtolower($alias);
|
||||
if(count($targets) > 0){
|
||||
$this->knownCommands[strtolower($alias)] = new FormattedCommandAlias(strtolower($alias), $targets);
|
||||
$this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets);
|
||||
}else{
|
||||
unset($this->knownCommands[strtolower($alias)]);
|
||||
unset($this->knownCommands[$lowerAlias]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function date;
|
||||
|
||||
class DumpMemoryCommand extends VanillaCommand{
|
||||
|
@ -63,7 +63,7 @@ class GarbageCollectorCommand extends VanillaCommand{
|
||||
|
||||
$cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector();
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::WHITE, TextFormat::GREEN . " ----" . TextFormat::WHITE));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::RESET, TextFormat::GREEN . " ----" . TextFormat::RESET));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_chunks(TextFormat::RED . number_format($chunksCollected))->prefix(TextFormat::GOLD));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD));
|
||||
|
||||
|
@ -76,7 +76,6 @@ class HelpCommand extends VanillaCommand{
|
||||
$pageHeight = $sender->getScreenLineHeight();
|
||||
|
||||
if($commandName === ""){
|
||||
/** @var Command[][] $commands */
|
||||
$commands = [];
|
||||
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
|
||||
if($command->testPermissionSilent($sender)){
|
||||
@ -95,7 +94,7 @@ class HelpCommand extends VanillaCommand{
|
||||
foreach($commands[$pageNumber - 1] as $command){
|
||||
$description = $command->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::WHITE . $descriptionString);
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,18 +106,18 @@ class HelpCommand extends VanillaCommand{
|
||||
$description = $cmd->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::WHITE, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::WHITE . $descriptionString)
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$usage = $cmd->getUsage();
|
||||
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $usageString)))
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$aliases = $cmd->getAliases();
|
||||
sort($aliases, SORT_NATURAL);
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::WHITE . implode(", ", $aliases))
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
return true;
|
||||
|
@ -52,7 +52,7 @@ class MeCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::WHITE . implode(" ", $args)));
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class PluginsCommand extends VanillaCommand{
|
||||
}, $sender->getServer()->getPluginManager()->getPlugins());
|
||||
sort($list, SORT_STRING);
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::WHITE . ", ", $list)));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::RESET . ", ", $list)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class StatusCommand extends VanillaCommand{
|
||||
$mUsage = Process::getAdvancedMemoryUsage();
|
||||
|
||||
$server = $sender->getServer();
|
||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
|
||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::RESET . "Server status" . TextFormat::GREEN . " ----");
|
||||
|
||||
$time = (int) (microtime(true) - $server->getStartTime());
|
||||
|
||||
@ -82,10 +82,10 @@ class StatusCommand extends VanillaCommand{
|
||||
$sender->sendMessage(TextFormat::GOLD . "Uptime: " . TextFormat::RED . $uptime);
|
||||
|
||||
$tpsColor = TextFormat::GREEN;
|
||||
if($server->getTicksPerSecond() < 17){
|
||||
$tpsColor = TextFormat::GOLD;
|
||||
}elseif($server->getTicksPerSecond() < 12){
|
||||
if($server->getTicksPerSecond() < 12){
|
||||
$tpsColor = TextFormat::RED;
|
||||
}elseif($server->getTicksPerSecond() < 17){
|
||||
$tpsColor = TextFormat::GOLD;
|
||||
}
|
||||
|
||||
$sender->sendMessage(TextFormat::GOLD . "Current TPS: {$tpsColor}{$server->getTicksPerSecond()} ({$server->getTickUsage()}%)");
|
||||
|
@ -35,7 +35,7 @@ use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\InternetException;
|
||||
use pocketmine\utils\InternetRequestResult;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function count;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
|
@ -57,17 +57,18 @@ class VersionCommand extends VanillaCommand{
|
||||
|
||||
if(count($args) === 0){
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareName(
|
||||
VersionInfo::NAME
|
||||
TextFormat::GREEN . VersionInfo::NAME . TextFormat::RESET
|
||||
));
|
||||
$versionColor = VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : TextFormat::GREEN;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareVersion(
|
||||
VersionInfo::VERSION()->getFullVersion(),
|
||||
VersionInfo::GIT_HASH()
|
||||
$versionColor . VersionInfo::VERSION()->getFullVersion() . TextFormat::RESET,
|
||||
TextFormat::GREEN . VersionInfo::GIT_HASH() . TextFormat::RESET
|
||||
));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_minecraftVersion(
|
||||
ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
(string) ProtocolInfo::CURRENT_PROTOCOL
|
||||
TextFormat::GREEN . ProtocolInfo::MINECRAFT_VERSION_NETWORK . TextFormat::RESET,
|
||||
TextFormat::GREEN . ProtocolInfo::CURRENT_PROTOCOL . TextFormat::RESET
|
||||
));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(PHP_VERSION));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(TextFormat::GREEN . PHP_VERSION . TextFormat::RESET));
|
||||
|
||||
$jitMode = Utils::getOpcacheJitMode();
|
||||
if($jitMode !== null){
|
||||
@ -79,8 +80,8 @@ class VersionCommand extends VanillaCommand{
|
||||
}else{
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitNotSupported();
|
||||
}
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(Utils::getOS()));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus->format(TextFormat::GREEN, TextFormat::RESET)));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(TextFormat::GREEN . Utils::getOS() . TextFormat::RESET));
|
||||
}else{
|
||||
$pluginName = implode(" ", $args);
|
||||
$exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName);
|
||||
@ -110,7 +111,7 @@ class VersionCommand extends VanillaCommand{
|
||||
|
||||
private function describeToSender(Plugin $plugin, CommandSender $sender) : void{
|
||||
$desc = $plugin->getDescription();
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::WHITE . " version " . TextFormat::DARK_GREEN . $desc->getVersion());
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::RESET . " version " . TextFormat::DARK_GREEN . $desc->getVersion());
|
||||
|
||||
if($desc->getDescription() !== ""){
|
||||
$sender->sendMessage($desc->getDescription());
|
||||
|
@ -30,6 +30,8 @@ use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\permission\PermissibleBase;
|
||||
use pocketmine\permission\PermissibleDelegateTrait;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function explode;
|
||||
use function trim;
|
||||
use const PHP_INT_MAX;
|
||||
@ -59,13 +61,12 @@ class ConsoleCommandSender implements CommandSender{
|
||||
}
|
||||
|
||||
public function sendMessage(Translatable|string $message) : void{
|
||||
$server = $this->getServer();
|
||||
if($message instanceof Translatable){
|
||||
$message = $this->getLanguage()->translate($message);
|
||||
}
|
||||
|
||||
foreach(explode("\n", trim($message)) as $line){
|
||||
$server->getLogger()->info($line);
|
||||
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\utils\Process;
|
||||
use function cli_set_process_title;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function feof;
|
||||
use function fwrite;
|
||||
use function stream_socket_client;
|
||||
use const PTHREADS_INHERIT_NONE;
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
@ -43,9 +45,40 @@ $socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
|
||||
if($socket === false){
|
||||
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
|
||||
}
|
||||
$consoleReader = new ConsoleReader();
|
||||
|
||||
$channel = new \Threaded();
|
||||
$thread = new class($channel) extends \Thread{
|
||||
public function __construct(
|
||||
private \Threaded $channel,
|
||||
){}
|
||||
|
||||
public function run(){
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
$channel = $this->channel;
|
||||
$reader = new ConsoleReader();
|
||||
while(true){ // @phpstan-ignore-line
|
||||
$line = $reader->readLine();
|
||||
if($line !== null){
|
||||
$channel->synchronized(function() use ($channel, $line) : void{
|
||||
$channel[] = $line;
|
||||
$channel->notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$thread->start(PTHREADS_INHERIT_NONE);
|
||||
while(!feof($socket)){
|
||||
$line = $consoleReader->readLine();
|
||||
$line = $channel->synchronized(function() use ($channel) : ?string{
|
||||
if(count($channel) === 0){
|
||||
$channel->wait(1_000_000);
|
||||
}
|
||||
/** @var string|null $line */
|
||||
$line = $channel->shift();
|
||||
return $line;
|
||||
});
|
||||
if(@fwrite($socket, ($line ?? "") . "\n") === false){
|
||||
//Always send even if there's no line, to check if the parent is alive
|
||||
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
|
||||
@ -53,3 +86,8 @@ while(!feof($socket)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//For simplicity's sake, we don't bother with a graceful shutdown here.
|
||||
//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the
|
||||
//parent process was terminated forcibly and didn't clean up after itself.
|
||||
Process::kill(Process::pid(), false);
|
||||
|
@ -23,11 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\thread\Thread;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function base64_encode;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
@ -45,36 +43,35 @@ use function trim;
|
||||
use const PHP_BINARY;
|
||||
use const STREAM_SHUT_RDWR;
|
||||
|
||||
final class ConsoleReaderThread extends Thread{
|
||||
/**
|
||||
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
|
||||
* properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets()
|
||||
* waiting for a line that might never come (and Windows doesn't support character-based reading either), and
|
||||
* pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless.
|
||||
*
|
||||
* This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill
|
||||
* the entire server process to make it go away.
|
||||
*
|
||||
* To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can
|
||||
* then brutally murder when the server shuts down, without killing the entire server process.
|
||||
* Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process
|
||||
* communication.
|
||||
*/
|
||||
final class ConsoleReaderChildProcessDaemon{
|
||||
private \PrefixedLogger $logger;
|
||||
/** @var resource */
|
||||
private $subprocess;
|
||||
/** @var resource */
|
||||
private $socket;
|
||||
|
||||
public function __construct(
|
||||
private \Threaded $buffer,
|
||||
private ?SleeperNotifier $notifier = null
|
||||
){}
|
||||
|
||||
protected function onRun() : void{
|
||||
$buffer = $this->buffer;
|
||||
$notifier = $this->notifier;
|
||||
|
||||
while(!$this->isKilled){
|
||||
$this->runSubprocess($buffer, $notifier);
|
||||
}
|
||||
\Logger $logger
|
||||
){
|
||||
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
|
||||
$this->prepareSubprocess();
|
||||
}
|
||||
|
||||
/**
|
||||
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
|
||||
* properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets()
|
||||
* waiting for a line that might never come (and Windows doesn't support character-based reading either), and
|
||||
* pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless.
|
||||
*
|
||||
* This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill
|
||||
* the entire server process to make it go away.
|
||||
*
|
||||
* To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can
|
||||
* then brutally murder when the server shuts down, without killing the entire server process.
|
||||
* Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process
|
||||
* communication.
|
||||
*/
|
||||
private function runSubprocess(\Threaded $buffer, ?SleeperNotifier $notifier) : void{
|
||||
private function prepareSubprocess() : void{
|
||||
$server = stream_socket_server("tcp://127.0.0.1:0");
|
||||
if($server === false){
|
||||
throw new \RuntimeException("Failed to open console reader socket server");
|
||||
@ -96,41 +93,43 @@ final class ConsoleReaderThread extends Thread{
|
||||
throw new AssumptionFailedError("stream_socket_accept() returned false");
|
||||
}
|
||||
stream_socket_shutdown($server, STREAM_SHUT_RDWR);
|
||||
while(!$this->isKilled){
|
||||
$r = [$client];
|
||||
$w = null;
|
||||
$e = null;
|
||||
if(stream_select($r, $w, $e, 0, 200000) === 1){
|
||||
$command = fgets($client);
|
||||
if($command === false){
|
||||
//subprocess died for some reason; this could be someone killed it manually from outside (e.g.
|
||||
//mistyped PID) or it might be a ctrl+c signal to this process that the child is handling
|
||||
//differently (different signal handlers).
|
||||
//since we have no way to know the difference, we just kill the sub and start a new one.
|
||||
break;
|
||||
}
|
||||
|
||||
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
if($command === ""){
|
||||
continue;
|
||||
}
|
||||
$buffer[] = $command;
|
||||
if($notifier !== null){
|
||||
$notifier->wakeupSleeper();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->subprocess = $sub;
|
||||
$this->socket = $client;
|
||||
}
|
||||
|
||||
private function shutdownSubprocess() : void{
|
||||
//we have no way to signal to the subprocess to shut down gracefully; besides, Windows sucks, and the subprocess
|
||||
//gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in
|
||||
//the first place).
|
||||
proc_terminate($sub);
|
||||
proc_close($sub);
|
||||
stream_socket_shutdown($client, STREAM_SHUT_RDWR);
|
||||
proc_terminate($this->subprocess);
|
||||
proc_close($this->subprocess);
|
||||
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
|
||||
}
|
||||
|
||||
public function getThreadName() : string{
|
||||
return "Console";
|
||||
public function readLine() : ?string{
|
||||
$r = [$this->socket];
|
||||
$w = null;
|
||||
$e = null;
|
||||
if(stream_select($r, $w, $e, 0, 0) === 1){
|
||||
$command = fgets($this->socket);
|
||||
if($command === false){
|
||||
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
|
||||
$this->shutdownSubprocess();
|
||||
$this->prepareSubprocess();
|
||||
return null;
|
||||
}
|
||||
|
||||
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
|
||||
return $command !== "" ? $command : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function quit() : void{
|
||||
$this->shutdownSubprocess();
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function base64_encode;
|
||||
use function error_get_last;
|
||||
use function file;
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyBiomeIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
|
||||
use SingletonTrait;
|
||||
|
@ -250,10 +250,8 @@ abstract class Entity{
|
||||
$this->getWorld()->addEntity($this);
|
||||
|
||||
$this->lastUpdate = $this->server->getTick();
|
||||
(new EntitySpawnEvent($this))->call();
|
||||
|
||||
$this->scheduleUpdate();
|
||||
|
||||
}
|
||||
|
||||
abstract protected function getInitialSizeInfo() : EntitySizeInfo;
|
||||
@ -938,6 +936,14 @@ abstract class Entity{
|
||||
return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from onUpdate() on the first tick of a new entity. This is called before any movement processing or
|
||||
* main ticking logic. Use this to fire any events related to spawning the entity.
|
||||
*/
|
||||
protected function onFirstUpdate(int $currentTick) : void{
|
||||
(new EntitySpawnEvent($this))->call();
|
||||
}
|
||||
|
||||
public function onUpdate(int $currentTick) : bool{
|
||||
if($this->closed){
|
||||
return false;
|
||||
@ -954,6 +960,10 @@ abstract class Entity{
|
||||
|
||||
$this->lastUpdate = $currentTick;
|
||||
|
||||
if($this->justCreated){
|
||||
$this->onFirstUpdate($currentTick);
|
||||
}
|
||||
|
||||
if(!$this->isAlive()){
|
||||
if($this->onDeathUpdate($tickDiff)){
|
||||
$this->flagForDespawn();
|
||||
@ -988,9 +998,7 @@ abstract class Entity{
|
||||
|
||||
$this->timings->stopTiming();
|
||||
|
||||
//if($this->isStatic())
|
||||
return ($hasUpdate || $this->hasMovementUpdate());
|
||||
//return !($this instanceof Player);
|
||||
}
|
||||
|
||||
final public function scheduleUpdate() : void{
|
||||
|
@ -175,20 +175,6 @@ final class EntityFactory{
|
||||
}, ['Human']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(World, CompoundTag) : Entity $creationFunc
|
||||
*/
|
||||
private static function validateCreationFunc(\Closure $creationFunc) : void{
|
||||
$sig = new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
);
|
||||
if(!$sig->isSatisfiedBy($creationFunc)){
|
||||
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($creationFunc) . "` must be compatible with `" . $sig . "`");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity type into the index.
|
||||
*
|
||||
@ -207,7 +193,11 @@ final class EntityFactory{
|
||||
throw new \InvalidArgumentException("At least one save name must be provided");
|
||||
}
|
||||
Utils::testValidInstance($className, Entity::class);
|
||||
self::validateCreationFunc($creationFunc);
|
||||
Utils::validateCallableSignature(new CallbackType(
|
||||
new ReturnType(Entity::class),
|
||||
new ParameterType("world", World::class),
|
||||
new ParameterType("nbt", CompoundTag::class)
|
||||
), $creationFunc);
|
||||
|
||||
foreach($saveNames as $name){
|
||||
$this->creationFuncs[$name] = $creationFunc;
|
||||
|
@ -356,7 +356,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
&& ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){
|
||||
|
||||
$compensation = $this->getHealth() - $source->getFinalDamage() - 1;
|
||||
if($compensation < 0){
|
||||
if($compensation <= -1){
|
||||
$source->setModifier($compensation, EntityDamageEvent::MODIFIER_TOTEM);
|
||||
}
|
||||
}
|
||||
|
@ -293,6 +293,10 @@ abstract class Living extends Entity{
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function always returns true, no matter whether the target is in the line of sight or not.
|
||||
* @see VoxelRayTrace::inDirection() for a more generalized method of ray-tracing to a target.
|
||||
*/
|
||||
public function hasLineOfSight(Entity $entity) : bool{
|
||||
//TODO: head height
|
||||
return true;
|
||||
|
@ -33,7 +33,7 @@ class InstantDamageEffect extends InstantEffect{
|
||||
|
||||
public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null) : void{
|
||||
//TODO: add particles (witch spell)
|
||||
$damage = (4 << $instance->getAmplifier()) * $potency;
|
||||
$damage = (6 << $instance->getAmplifier()) * $potency;
|
||||
if($source !== null){
|
||||
$sourceOwner = $source->getOwningEntity();
|
||||
if($sourceOwner !== null){
|
||||
|
@ -30,7 +30,7 @@ use pocketmine\event\entity\EntityRegainHealthEvent;
|
||||
class RegenerationEffect extends Effect{
|
||||
|
||||
public function canTick(EffectInstance $instance) : bool{
|
||||
if(($interval = (40 >> $instance->getAmplifier())) > 0){
|
||||
if(($interval = (50 >> $instance->getAmplifier())) > 0){
|
||||
return ($instance->getDuration() % $interval) === 0;
|
||||
}
|
||||
return true;
|
||||
|
@ -92,8 +92,11 @@ class ItemEntity extends Entity{
|
||||
$this->pickupDelay = $nbt->getShort("PickupDelay", $this->pickupDelay);
|
||||
$this->owner = $nbt->getString("Owner", $this->owner);
|
||||
$this->thrower = $nbt->getString("Thrower", $this->thrower);
|
||||
}
|
||||
|
||||
(new ItemSpawnEvent($this))->call();
|
||||
protected function onFirstUpdate(int $currentTick) : void{
|
||||
(new ItemSpawnEvent($this))->call(); //this must be called before EntitySpawnEvent, to maintain backwards compatibility
|
||||
parent::onFirstUpdate($currentTick);
|
||||
}
|
||||
|
||||
protected function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function max;
|
||||
@ -85,6 +86,7 @@ abstract class BaseInventory implements Inventory{
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void{
|
||||
Utils::validateArrayValueType($items, function(Item $item) : void{});
|
||||
if(count($items) > $this->getSize()){
|
||||
$items = array_slice($items, 0, $this->getSize(), true);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace pocketmine\inventory;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function json_decode;
|
||||
|
||||
|
@ -24,8 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* An inventory which is backed by another inventory, and acts as a proxy to that inventory.
|
||||
@ -37,16 +35,25 @@ class DelegateInventory extends BaseInventory{
|
||||
private Inventory $backingInventory
|
||||
){
|
||||
parent::__construct();
|
||||
$weakThis = \WeakReference::create($this);
|
||||
$this->backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener(
|
||||
function(Inventory $unused, int $slot, Item $oldItem) : void{
|
||||
$this->onSlotChange($slot, $oldItem);
|
||||
static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onSlotChange($slot, $oldItem);
|
||||
}
|
||||
},
|
||||
function(Inventory $unused, array $oldContents) : void{
|
||||
$this->onContentChange($oldContents);
|
||||
static function(Inventory $unused, array $oldContents) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onContentChange($oldContents);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
$this->backingInventory->getListeners()->remove($this->inventoryListener);
|
||||
}
|
||||
|
||||
public function getSize() : int{
|
||||
return $this->backingInventory->getSize();
|
||||
}
|
||||
@ -66,12 +73,4 @@ class DelegateInventory extends BaseInventory{
|
||||
protected function internalSetContents(array $items) : void{
|
||||
$this->backingInventory->setContents($items);
|
||||
}
|
||||
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
if(count($this->getViewers()) === 0 && count($this->getListeners()->toArray()) === 1){
|
||||
$this->backingInventory->getListeners()->remove($this->inventoryListener);
|
||||
$this->inventoryListener = CallbackInventoryListener::onAnyChange(static function() : void{}); //break cyclic reference
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,8 @@ class InventoryTransaction{
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
* @phpstan-param-out Item[] $needItems
|
||||
* @phpstan-param-out Item[] $haveItems
|
||||
*
|
||||
* @throws TransactionValidationException
|
||||
*/
|
||||
|
61
src/inventory/transaction/TransactionBuilder.php
Normal file
61
src/inventory/transaction/TransactionBuilder.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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\inventory\transaction;
|
||||
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\transaction\action\InventoryAction;
|
||||
use function spl_object_id;
|
||||
|
||||
final class TransactionBuilder{
|
||||
|
||||
/** @var TransactionBuilderInventory[] */
|
||||
private array $inventories = [];
|
||||
|
||||
/** @var InventoryAction[] */
|
||||
private array $extraActions = [];
|
||||
|
||||
public function addAction(InventoryAction $action) : void{
|
||||
$this->extraActions[spl_object_id($action)] = $action;
|
||||
}
|
||||
|
||||
public function getInventory(Inventory $inventory) : TransactionBuilderInventory{
|
||||
$id = spl_object_id($inventory);
|
||||
return $this->inventories[$id] ??= new TransactionBuilderInventory($inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InventoryAction[]
|
||||
*/
|
||||
public function generateActions() : array{
|
||||
$actions = $this->extraActions;
|
||||
|
||||
foreach($this->inventories as $inventory){
|
||||
foreach($inventory->generateActions() as $action){
|
||||
$actions[spl_object_id($action)] = $action;
|
||||
}
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
}
|
@ -86,6 +86,12 @@ class Armor extends Durable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function clearCustomColor() : self{
|
||||
$this->customColor = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total enchantment protection factor this armour piece offers from all applicable protection
|
||||
* enchantments on the item.
|
||||
|
@ -109,6 +109,7 @@ abstract class Durable extends Item{
|
||||
*/
|
||||
protected function onBroken() : void{
|
||||
$this->pop();
|
||||
$this->setDamage(0); //the stack size may be greater than 1 if overstacked by a plugin
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ namespace pocketmine\item;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
@ -108,8 +108,9 @@ final class LegacyStringToItemParser{
|
||||
throw new LegacyStringToItemParserException("Unable to parse \"" . $b[1] . "\" from \"" . $input . "\" as a valid meta value");
|
||||
}
|
||||
|
||||
if(isset($this->map[strtolower($b[0])])){
|
||||
$item = $this->itemFactory->get($this->map[strtolower($b[0])], $meta);
|
||||
$id = strtolower($b[0]);
|
||||
if(isset($this->map[$id])){
|
||||
$item = $this->itemFactory->get($this->map[$id], $meta);
|
||||
}else{
|
||||
throw new LegacyStringToItemParserException("Unable to resolve \"" . $input . "\" to a valid item");
|
||||
}
|
||||
|
@ -743,6 +743,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL, []);
|
||||
}
|
||||
|
||||
public static function effect_darkness() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::EFFECT_DARKNESS, []);
|
||||
}
|
||||
|
||||
public static function enchantment_arrowDamage() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_ARROWDAMAGE, []);
|
||||
}
|
||||
@ -1393,6 +1397,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_userDefined_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_version_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_VERSION_DESCRIPTION, []);
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ final class KnownTranslationKeys{
|
||||
public const DISCONNECTIONSCREEN_OUTDATEDSERVER = "disconnectionScreen.outdatedServer";
|
||||
public const DISCONNECTIONSCREEN_RESOURCEPACK = "disconnectionScreen.resourcePack";
|
||||
public const DISCONNECTIONSCREEN_SERVERFULL = "disconnectionScreen.serverFull";
|
||||
public const EFFECT_DARKNESS = "effect.darkness";
|
||||
public const ENCHANTMENT_ARROWDAMAGE = "enchantment.arrowDamage";
|
||||
public const ENCHANTMENT_ARROWFIRE = "enchantment.arrowFire";
|
||||
public const ENCHANTMENT_ARROWINFINITE = "enchantment.arrowInfinite";
|
||||
@ -306,6 +307,7 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_COMMAND_TRANSFERSERVER_USAGE = "pocketmine.command.transferserver.usage";
|
||||
public const POCKETMINE_COMMAND_UNBAN_IP_DESCRIPTION = "pocketmine.command.unban.ip.description";
|
||||
public const POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION = "pocketmine.command.unban.player.description";
|
||||
public const POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION = "pocketmine.command.userDefined.description";
|
||||
public const POCKETMINE_COMMAND_VERSION_DESCRIPTION = "pocketmine.command.version.description";
|
||||
public const POCKETMINE_COMMAND_VERSION_MINECRAFTVERSION = "pocketmine.command.version.minecraftVersion";
|
||||
public const POCKETMINE_COMMAND_VERSION_NOSUCHPLUGIN = "pocketmine.command.version.noSuchPlugin";
|
||||
|
@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function count;
|
||||
|
64
src/network/mcpe/ComplexWindowMapEntry.php
Normal file
64
src/network/mcpe/ComplexWindowMapEntry.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\inventory\Inventory;
|
||||
|
||||
final class ComplexWindowMapEntry{
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private array $reverseSlotMap = [];
|
||||
|
||||
/**
|
||||
* @param int[] $slotMap
|
||||
* @phpstan-param array<int, int> $slotMap
|
||||
*/
|
||||
public function __construct(
|
||||
private Inventory $inventory,
|
||||
private array $slotMap
|
||||
){
|
||||
foreach($slotMap as $slot => $index){
|
||||
$this->reverseSlotMap[$index] = $slot;
|
||||
}
|
||||
}
|
||||
|
||||
public function getInventory() : Inventory{ return $this->inventory; }
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array<int, int>
|
||||
*/
|
||||
public function getSlotMap() : array{ return $this->slotMap; }
|
||||
|
||||
public function mapNetToCore(int $slot) : ?int{
|
||||
return $this->slotMap[$slot] ?? null;
|
||||
}
|
||||
|
||||
public function mapCoreToNet(int $slot) : ?int{
|
||||
return $this->reverseSlotMap[$slot] ?? null;
|
||||
}
|
||||
}
|
@ -53,6 +53,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;
|
||||
use pocketmine\network\PacketHandlingException;
|
||||
use pocketmine\player\Player;
|
||||
@ -61,6 +62,7 @@ use pocketmine\utils\ObjectSet;
|
||||
use function array_map;
|
||||
use function array_search;
|
||||
use function get_class;
|
||||
use function is_int;
|
||||
use function max;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -70,6 +72,17 @@ use function spl_object_id;
|
||||
class InventoryManager{
|
||||
/** @var Inventory[] */
|
||||
private array $windowMap = [];
|
||||
/**
|
||||
* @var ComplexWindowMapEntry[]
|
||||
* @phpstan-var array<int, ComplexWindowMapEntry>
|
||||
*/
|
||||
private array $complexWindows = [];
|
||||
/**
|
||||
* @var ComplexWindowMapEntry[]
|
||||
* @phpstan-var array<int, ComplexWindowMapEntry>
|
||||
*/
|
||||
private array $complexSlotToWindowMap = [];
|
||||
|
||||
private int $lastInventoryNetworkId = ContainerIds::FIRST;
|
||||
|
||||
/**
|
||||
@ -96,7 +109,8 @@ class InventoryManager{
|
||||
$this->add(ContainerIds::INVENTORY, $this->player->getInventory());
|
||||
$this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory());
|
||||
$this->add(ContainerIds::ARMOR, $this->player->getArmorInventory());
|
||||
$this->add(ContainerIds::UI, $this->player->getCursorInventory());
|
||||
$this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory());
|
||||
$this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid());
|
||||
|
||||
$this->player->getInventory()->getHeldItemIndexChangeListeners()->add(function() : void{
|
||||
$this->syncSelectedHotbarSlot();
|
||||
@ -113,8 +127,27 @@ class InventoryManager{
|
||||
return $this->lastInventoryNetworkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|int $slotMap
|
||||
* @phpstan-param array<int, int>|int $slotMap
|
||||
*/
|
||||
private function addComplex(array|int $slotMap, Inventory $inventory) : void{
|
||||
$entry = new ComplexWindowMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap);
|
||||
$this->complexWindows[spl_object_id($inventory)] = $entry;
|
||||
foreach($entry->getSlotMap() as $netSlot => $coreSlot){
|
||||
$this->complexSlotToWindowMap[$netSlot] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
private function remove(int $id) : void{
|
||||
unset($this->windowMap[$id], $this->initiatedSlotChanges[$id]);
|
||||
$inventory = $this->windowMap[$id];
|
||||
$splObjectId = spl_object_id($inventory);
|
||||
unset($this->windowMap[$id], $this->initiatedSlotChanges[$id], $this->complexWindows[$splObjectId]);
|
||||
foreach($this->complexSlotToWindowMap as $netSlot => $entry){
|
||||
if($entry->getInventory() === $inventory){
|
||||
unset($this->complexSlotToWindowMap[$netSlot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getWindowId(Inventory $inventory) : ?int{
|
||||
@ -125,8 +158,22 @@ class InventoryManager{
|
||||
return $this->lastInventoryNetworkId;
|
||||
}
|
||||
|
||||
public function getWindow(int $windowId) : ?Inventory{
|
||||
return $this->windowMap[$windowId] ?? null;
|
||||
/**
|
||||
* @phpstan-return array{Inventory, int}
|
||||
*/
|
||||
public function locateWindowAndSlot(int $windowId, int $netSlotId) : ?array{
|
||||
if($windowId === ContainerIds::UI){
|
||||
$entry = $this->complexSlotToWindowMap[$netSlotId] ?? null;
|
||||
if($entry === null){
|
||||
return null;
|
||||
}
|
||||
$coreSlotId = $entry->mapNetToCore($netSlotId);
|
||||
return $coreSlotId !== null ? [$entry->getInventory(), $coreSlotId] : null;
|
||||
}
|
||||
if(isset($this->windowMap[$windowId])){
|
||||
return [$this->windowMap[$windowId], $netSlotId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onTransactionStart(InventoryTransaction $tx) : void{
|
||||
@ -179,11 +226,30 @@ class InventoryManager{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]|null
|
||||
* @phpstan-return array<int, int>|null
|
||||
*/
|
||||
private function createComplexSlotMapping(Inventory $inventory) : ?array{
|
||||
//TODO: make this dynamic so plugins can add mappings for stuff not implemented by PM
|
||||
return match(true){
|
||||
$inventory instanceof AnvilInventory => UIInventorySlotOffset::ANVIL,
|
||||
$inventory instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE,
|
||||
$inventory instanceof LoomInventory => UIInventorySlotOffset::LOOM,
|
||||
$inventory instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT],
|
||||
$inventory instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function onCurrentWindowChange(Inventory $inventory) : void{
|
||||
$this->onCurrentWindowRemove();
|
||||
|
||||
$this->openWindowDeferred(function() use ($inventory) : void{
|
||||
$windowId = $this->addDynamic($inventory);
|
||||
if(($slotMap = $this->createComplexSlotMapping($inventory)) !== null){
|
||||
$this->addComplex($slotMap, $inventory);
|
||||
}
|
||||
|
||||
foreach($this->containerOpenCallbacks as $callback){
|
||||
$pks = $callback($windowId, $inventory);
|
||||
@ -282,10 +348,17 @@ class InventoryManager{
|
||||
}
|
||||
|
||||
public function syncSlot(Inventory $inventory, int $slot) : void{
|
||||
$windowId = $this->getWindowId($inventory);
|
||||
if($windowId !== null){
|
||||
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
|
||||
if($slotMap !== null){
|
||||
$windowId = ContainerIds::UI;
|
||||
$netSlot = $slotMap->mapCoreToNet($slot) ?? null;
|
||||
}else{
|
||||
$windowId = $this->getWindowId($inventory);
|
||||
$netSlot = $slot;
|
||||
}
|
||||
if($windowId !== null && $netSlot !== null){
|
||||
$currentItem = $inventory->getItem($slot);
|
||||
$clientSideItem = $this->initiatedSlotChanges[$windowId][$slot] ?? null;
|
||||
$clientSideItem = $this->initiatedSlotChanges[$windowId][$netSlot] ?? null;
|
||||
if($clientSideItem === null || !$clientSideItem->equalsExact($currentItem)){
|
||||
$itemStackWrapper = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($currentItem));
|
||||
if($windowId === ContainerIds::OFFHAND){
|
||||
@ -296,30 +369,37 @@ class InventoryManager{
|
||||
//BDS (Bedrock Dedicated Server) also seems to work this way.
|
||||
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
|
||||
}else{
|
||||
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, $itemStackWrapper));
|
||||
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
|
||||
}
|
||||
}
|
||||
unset($this->initiatedSlotChanges[$windowId][$slot]);
|
||||
unset($this->initiatedSlotChanges[$windowId][$netSlot]);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncContents(Inventory $inventory) : void{
|
||||
$windowId = $this->getWindowId($inventory);
|
||||
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
|
||||
if($slotMap !== null){
|
||||
$windowId = ContainerIds::UI;
|
||||
}else{
|
||||
$windowId = $this->getWindowId($inventory);
|
||||
}
|
||||
$typeConverter = TypeConverter::getInstance();
|
||||
if($windowId !== null){
|
||||
unset($this->initiatedSlotChanges[$windowId]);
|
||||
$typeConverter = TypeConverter::getInstance();
|
||||
if($windowId === ContainerIds::UI){
|
||||
//TODO: HACK!
|
||||
//Since 1.13, cursor is now part of a larger "UI inventory", and sending contents for this larger inventory does
|
||||
//not work the way it's intended to. Even if it did, it would be necessary to send all 51 slots just to update
|
||||
//this one, which is just not worth it.
|
||||
//This workaround isn't great, but it's at least simple.
|
||||
$this->session->sendDataPacket(InventorySlotPacket::create(
|
||||
$windowId,
|
||||
0,
|
||||
ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($inventory->getItem(0)))
|
||||
));
|
||||
if($slotMap !== null){
|
||||
foreach($inventory->getContents(true) as $slotId => $item){
|
||||
$packetSlot = $slotMap->mapCoreToNet($slotId) ?? null;
|
||||
if($packetSlot === null){
|
||||
continue;
|
||||
}
|
||||
unset($this->initiatedSlotChanges[$windowId][$packetSlot]);
|
||||
$this->session->sendDataPacket(InventorySlotPacket::create(
|
||||
$windowId,
|
||||
$packetSlot,
|
||||
ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($inventory->getItem($slotId)))
|
||||
));
|
||||
}
|
||||
}else{
|
||||
unset($this->initiatedSlotChanges[$windowId]);
|
||||
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, array_map(function(Item $itemStack) use ($typeConverter) : ItemStackWrapper{
|
||||
return ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($itemStack));
|
||||
}, $inventory->getContents(true))));
|
||||
@ -331,16 +411,20 @@ class InventoryManager{
|
||||
foreach($this->windowMap as $inventory){
|
||||
$this->syncContents($inventory);
|
||||
}
|
||||
foreach($this->complexWindows as $entry){
|
||||
$this->syncContents($entry->getInventory());
|
||||
}
|
||||
}
|
||||
|
||||
public function syncMismatchedPredictedSlotChanges() : void{
|
||||
foreach($this->initiatedSlotChanges as $windowId => $slots){
|
||||
if(!isset($this->windowMap[$windowId])){
|
||||
continue;
|
||||
}
|
||||
$inventory = $this->windowMap[$windowId];
|
||||
foreach($slots as $netSlot => $expectedItem){
|
||||
$located = $this->locateWindowAndSlot($windowId, $netSlot);
|
||||
if($located === null){
|
||||
continue;
|
||||
}
|
||||
[$inventory, $slot] = $located;
|
||||
|
||||
foreach($slots as $slot => $expectedItem){
|
||||
if(!$inventory->slotExists($slot)){
|
||||
continue; //TODO: size desync ???
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTitlePacket;
|
||||
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\TextPacket;
|
||||
use pocketmine\network\mcpe\protocol\ToastRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\TransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
||||
@ -390,9 +391,8 @@ class NetworkSession{
|
||||
throw new PacketHandlingException("Unexpected non-serverbound packet");
|
||||
}
|
||||
|
||||
$timings = Timings::getReceiveDataPacketTimings($packet);
|
||||
$timings = Timings::getDecodeDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
|
||||
try{
|
||||
$stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext);
|
||||
try{
|
||||
@ -404,7 +404,15 @@ class NetworkSession{
|
||||
$remains = substr($stream->getBuffer(), $stream->getOffset());
|
||||
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
|
||||
}
|
||||
}finally{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
|
||||
$timings = Timings::getHandleDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
try{
|
||||
//TODO: I'm not sure DataPacketReceiveEvent should be included in the handler timings, but it needs to be
|
||||
//included for now to ensure the receivePacket timings are counted the way they were before
|
||||
$ev = new DataPacketReceiveEvent($this, $packet);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled() && ($this->handler === null || !$packet->handle($this->handler))){
|
||||
@ -1094,6 +1102,10 @@ class NetworkSession{
|
||||
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
|
||||
}
|
||||
|
||||
public function onToastNotification(string $title, string $body) : void{
|
||||
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
if($this->info === null){
|
||||
if(time() >= $this->connectTime + 10){
|
||||
|
2
src/network/mcpe/cache/StaticPacketCache.php
vendored
2
src/network/mcpe/cache/StaticPacketCache.php
vendored
@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
class StaticPacketCache{
|
||||
|
@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
|
@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_key_exists;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
@ -169,6 +169,7 @@ final class ItemTranslator{
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param-out bool $isComplexMapping
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
* @throws TypeConversionException
|
||||
|
@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
/**
|
||||
|
@ -24,11 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\inventory\AnvilInventory;
|
||||
use pocketmine\block\inventory\CraftingTableInventory;
|
||||
use pocketmine\block\inventory\EnchantInventory;
|
||||
use pocketmine\block\inventory\LoomInventory;
|
||||
use pocketmine\block\inventory\StonecutterInventory;
|
||||
use pocketmine\inventory\transaction\action\CreateItemAction;
|
||||
use pocketmine\inventory\transaction\action\DestroyItemAction;
|
||||
use pocketmine\inventory\transaction\action\DropItemAction;
|
||||
@ -285,38 +280,12 @@ class TypeConverter{
|
||||
}
|
||||
switch($action->sourceType){
|
||||
case NetworkInventoryAction::SOURCE_CONTAINER:
|
||||
$window = null;
|
||||
if($action->windowId === ContainerIds::UI && $action->inventorySlot > 0){
|
||||
if($action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
|
||||
return null; //useless noise
|
||||
}
|
||||
$pSlot = $action->inventorySlot;
|
||||
|
||||
$slot = UIInventorySlotOffset::CRAFTING2X2_INPUT[$pSlot] ?? null;
|
||||
if($slot !== null){
|
||||
$window = $player->getCraftingGrid();
|
||||
}elseif(($current = $player->getCurrentWindow()) !== null){
|
||||
$slotMap = match(true){
|
||||
$current instanceof AnvilInventory => UIInventorySlotOffset::ANVIL,
|
||||
$current instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE,
|
||||
$current instanceof LoomInventory => UIInventorySlotOffset::LOOM,
|
||||
$current instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT],
|
||||
$current instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT,
|
||||
default => null
|
||||
};
|
||||
if($slotMap !== null){
|
||||
$window = $current;
|
||||
$slot = $slotMap[$pSlot] ?? null;
|
||||
}
|
||||
}
|
||||
if($slot === null){
|
||||
throw new TypeConversionException("Unmatched UI inventory slot offset $pSlot");
|
||||
}
|
||||
}else{
|
||||
$window = $inventoryManager->getWindow($action->windowId);
|
||||
$slot = $action->inventorySlot;
|
||||
if($action->windowId === ContainerIds::UI && $action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
|
||||
return null; //useless noise
|
||||
}
|
||||
if($window !== null){
|
||||
$located = $inventoryManager->locateWindowAndSlot($action->windowId, $action->inventorySlot);
|
||||
if($located !== null){
|
||||
[$window, $slot] = $located;
|
||||
return new SlotChangeAction($window, $slot, $old, $new);
|
||||
}
|
||||
|
||||
@ -338,15 +307,10 @@ class TypeConverter{
|
||||
|
||||
}
|
||||
case NetworkInventoryAction::SOURCE_TODO:
|
||||
//These types need special handling.
|
||||
switch($action->windowId){
|
||||
case NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT:
|
||||
case NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT:
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO: more stuff
|
||||
throw new TypeConversionException("No open container with window ID $action->windowId");
|
||||
//These are used to balance a transaction that involves special actions, like crafting, enchanting, etc.
|
||||
//The vanilla server just accepted these without verifying them. We don't need to care about them since
|
||||
//we verify crafting by checking for imbalances anyway.
|
||||
return null;
|
||||
default:
|
||||
throw new TypeConversionException("Unknown inventory source type $action->sourceType");
|
||||
}
|
||||
|
@ -340,12 +340,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$converter = TypeConverter::getInstance();
|
||||
foreach($data->getActions() as $networkInventoryAction){
|
||||
if(
|
||||
(
|
||||
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO && (
|
||||
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT ||
|
||||
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
|
||||
)
|
||||
) || (
|
||||
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO || (
|
||||
$this->craftingTransaction !== null &&
|
||||
!$networkInventoryAction->oldItem->getItemStack()->equals($networkInventoryAction->newItem->getItemStack()) &&
|
||||
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user