mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-19 15:35:52 +00:00
Merge branch 'next-minor' into stable
This commit is contained in:
commit
80a432d9ff
@ -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;
|
||||
|
92
changelogs/4.11-beta.md
Normal file
92
changelogs/4.11-beta.md
Normal file
@ -0,0 +1,92 @@
|
||||
**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.
|
||||
|
||||
# 4.11.0-BETA2
|
||||
Released 13th November 2022.
|
||||
|
||||
## Configuration
|
||||
- The `chunk-ticking.per-tick` setting is now deprecated, and will be removed in a future release.
|
||||
- The functionality of this setting has been removed, since it caused more problems than it solved.
|
||||
- Setting it to zero will still disable chunk ticking (for now), but this should now be done by setting `chunk-ticking.tick-radius` to `0` instead.
|
||||
|
||||
## Gameplay
|
||||
- Improved chunk random ticking:
|
||||
- Removed the limit on chunks ticked per tick, and its associated config option is no longer respected.
|
||||
- This change significantly improves crop and plant growth with large numbers of players, but may cause higher CPU usage.
|
||||
- This limit was causing a linear decrease in chunk ticking speed with larger numbers of players, leading to worsened gameplay experience.
|
||||
- Every chunk within the configured tick radius of a player will be ticked. Previously, chunks were randomly selected from the radius.
|
||||
- Implemented Darkness effect.
|
||||
|
||||
## API
|
||||
### `pocketmine\world`
|
||||
- The following new API methods have been added:
|
||||
- `public World->getChunkTickRadius() : int` - returns the world's simulation radius
|
||||
- `public World->setChunkTickRadius(int $radius) : void` - sets the world's simulation radius
|
||||
|
||||
## Internals
|
||||
- Non-arrow projectile damage is now unscaled. Scaling according to velocity is only applied to arrows. This currently doesn't cause any observable change in behaviour, but is required for future additions.
|
102
changelogs/4.11.md
Normal file
102
changelogs/4.11.md
Normal file
@ -0,0 +1,102 @@
|
||||
**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
|
||||
Released 25th 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.
|
||||
- User-defined `pocketmine.yml` custom commands now use a generic description which makes clear the command is config-defined.
|
||||
- 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`
|
||||
- The file must contain the raw key bytes, and must not end with a newline.
|
||||
|
||||
## Configuration
|
||||
- The `chunk-ticking.per-tick` setting is now deprecated, and will be removed in a future release.
|
||||
- The functionality of this setting has been removed, since it caused more problems than it solved.
|
||||
- Setting it to zero will still disable chunk ticking (for now), but this should now be done by setting `chunk-ticking.tick-radius` to `0` instead.
|
||||
|
||||
## 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 missing sounds when adding, rotating, or removing items in item frames.
|
||||
- Fixed modifier values for Instant Damage and Regeneration effects.
|
||||
- Implemented Darkness effect.
|
||||
- Improved chunk random ticking:
|
||||
- Removed the limit on chunks ticked per tick, and its associated config option is no longer respected.
|
||||
- This change significantly improves crop and plant growth with large numbers of players.
|
||||
- This limit was causing a linear decrease in chunk ticking speed with larger numbers of players, leading to worsened gameplay experience.
|
||||
- **Warning: This change will result in increased CPU usage if players are spread over a very large area.**
|
||||
- Every chunk within the configured tick radius of a player will be ticked. Previously, chunks were randomly selected from the radius.
|
||||
|
||||
## 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\event\block`
|
||||
- The following new classes have been added:
|
||||
- `BlockDeathEvent` - event called when coral or coral blocks die due to lack of water
|
||||
|
||||
### `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\lang`
|
||||
- The following new API methods have been added:
|
||||
- `public Language->getAll() : array<string, string>`
|
||||
|
||||
### `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
|
||||
|
||||
### `pocketmine\world`
|
||||
- The following new API methods have been added:
|
||||
- `public World->getChunkTickRadius() : int` - returns the world's simulation radius
|
||||
- `public World->setChunkTickRadius(int $radius) : void` - sets the world's simulation radius
|
||||
|
||||
### `pocketmine\world\sound`
|
||||
- The following new classes have been added:
|
||||
- `ItemFrameAddItemSound`
|
||||
- `ItemFrameRemoveItemSound`
|
||||
- `ItemFrameRotateItemSound`
|
||||
|
||||
## 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.
|
||||
- Non-arrow projectile damage is now unscaled. Scaling according to velocity is only applied to arrows. This currently doesn't cause any observable change in behaviour, but is required for future additions.
|
@ -35,13 +35,13 @@
|
||||
"fgrosse/phpasn1": "^2.3",
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"pocketmine/bedrock-data": "~1.12.0+bedrock-1.19.40",
|
||||
"pocketmine/bedrock-protocol": "~14.0.0+bedrock-1.19.40",
|
||||
"pocketmine/bedrock-protocol": "~16.0.0+bedrock-1.19.40",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/classloader": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/locale-data": "~2.9.0",
|
||||
"pocketmine/locale-data": "~2.11.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.4.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
@ -50,6 +50,7 @@
|
||||
"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": {
|
||||
|
340
composer.lock
generated
340
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": "9d5a8688ca7ac8143921659525641a00",
|
||||
"content-hash": "ff2bc73e9b0acccb1e63ddef2412fe31",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -275,16 +275,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "14.0.0+bedrock-1.19.40",
|
||||
"version": "16.0.0+bedrock-1.19.40",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "b455a742779fee94d25f931cc2cbf6b2c5d61c1f"
|
||||
"reference": "ce900ffa6a4cc07af92686f27d580dd2e2541382"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/b455a742779fee94d25f931cc2cbf6b2c5d61c1f",
|
||||
"reference": "b455a742779fee94d25f931cc2cbf6b2c5d61c1f",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/ce900ffa6a4cc07af92686f27d580dd2e2541382",
|
||||
"reference": "ce900ffa6a4cc07af92686f27d580dd2e2541382",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -298,7 +298,7 @@
|
||||
"ramsey/uuid": "^4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.8.8",
|
||||
"phpstan/phpstan": "1.9.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
@ -316,9 +316,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.19.40"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/16.0.0+bedrock-1.19.40"
|
||||
},
|
||||
"time": "2022-10-25T21:51:46+00:00"
|
||||
"time": "2022-11-19T16:11:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -536,16 +536,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.9.3",
|
||||
"version": "2.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "73db4397b4150b29819bf39cc371924cc2e3f502"
|
||||
"reference": "4b33d8fa53eda53d9662a7478806ebae2e4a5c53"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/73db4397b4150b29819bf39cc371924cc2e3f502",
|
||||
"reference": "73db4397b4150b29819bf39cc371924cc2e3f502",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/4b33d8fa53eda53d9662a7478806ebae2e4a5c53",
|
||||
"reference": "4b33d8fa53eda53d9662a7478806ebae2e4a5c53",
|
||||
"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.9.3"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.11.0"
|
||||
},
|
||||
"time": "2022-11-12T13:59:25+00:00"
|
||||
"time": "2022-11-25T14:24:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -1020,6 +1020,318 @@
|
||||
],
|
||||
"time": "2022-11-05T23:03:38+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.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-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.27.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-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-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.27.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-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-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.27.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-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.27.0",
|
||||
|
@ -119,8 +119,6 @@ chunk-sending:
|
||||
spawn-radius: 4
|
||||
|
||||
chunk-ticking:
|
||||
#Max amount of chunks processed each tick
|
||||
per-tick: 40
|
||||
#Radius of chunks around a player to tick
|
||||
tick-radius: 3
|
||||
#Number of blocks inside ticking areas' subchunks that get ticked every tick. Higher values will accelerate events
|
||||
@ -168,6 +166,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;
|
||||
|
@ -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,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.10.3";
|
||||
public const BASE_VERSION = "4.11.1";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\utils\CoralType;
|
||||
use pocketmine\block\utils\CoralTypeTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\event\block\BlockDeathEvent;
|
||||
use pocketmine\item\Item;
|
||||
|
||||
abstract class BaseCoral extends Transparent{
|
||||
@ -50,7 +51,11 @@ abstract class BaseCoral extends Transparent{
|
||||
|
||||
//TODO: check water inside the block itself (not supported on the API yet)
|
||||
if(!$hasWater){
|
||||
$world->setBlock($this->position, $this->setDead(true));
|
||||
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\utils\CoralType;
|
||||
use pocketmine\block\utils\CoralTypeTrait;
|
||||
use pocketmine\block\utils\InvalidBlockStateException;
|
||||
use pocketmine\data\bedrock\CoralTypeIdMap;
|
||||
use pocketmine\event\block\BlockDeathEvent;
|
||||
use pocketmine\item\Item;
|
||||
use function mt_rand;
|
||||
|
||||
@ -77,7 +78,11 @@ final class CoralBlock extends Opaque{
|
||||
}
|
||||
}
|
||||
if(!$hasWater){
|
||||
$world->setBlock($this->position, $this->setDead(true));
|
||||
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\ItemFrameAddItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRemoveItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRotateItemSound;
|
||||
use function is_infinite;
|
||||
use function is_nan;
|
||||
use function lcg_value;
|
||||
@ -136,8 +139,12 @@ class ItemFrame extends Flowable{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->framedItem !== null){
|
||||
$this->itemRotation = ($this->itemRotation + 1) % self::ROTATIONS;
|
||||
|
||||
$this->position->getWorld()->addSound($this->position, new ItemFrameRotateItemSound());
|
||||
}elseif(!$item->isNull()){
|
||||
$this->framedItem = $item->pop();
|
||||
|
||||
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
@ -151,11 +158,13 @@ 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);
|
||||
$world->addSound($this->position, new ItemFrameRemoveItemSound());
|
||||
}
|
||||
$this->setFramedItem(null);
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$world->setBlock($this->position, $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -124,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);
|
||||
|
@ -52,7 +52,7 @@ class FormattedCommandAlias extends Command{
|
||||
string $alias,
|
||||
private array $formatStrings
|
||||
){
|
||||
parent::__construct($alias);
|
||||
parent::__construct($alias, KnownTranslationFactory::pocketmine_command_userDefined_description());
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
|
@ -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));
|
||||
|
||||
|
@ -94,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -74,6 +74,7 @@ final class EffectIdMap{
|
||||
//TODO: SLOW_FALLING
|
||||
//TODO: BAD_OMEN
|
||||
//TODO: VILLAGE_HERO
|
||||
$this->register(EffectIds::DARKNESS, VanillaEffects::DARKNESS());
|
||||
}
|
||||
|
||||
//TODO: not a big fan of the code duplication here :(
|
||||
|
@ -58,4 +58,5 @@ final class EffectIds{
|
||||
public const SLOW_FALLING = 27;
|
||||
public const BAD_OMEN = 28;
|
||||
public const VILLAGE_HERO = 29;
|
||||
public const DARKNESS = 30;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -40,6 +40,7 @@ final class StringToEffectParser extends StringToTParser{
|
||||
$result->register("absorption", fn() => VanillaEffects::ABSORPTION());
|
||||
$result->register("blindness", fn() => VanillaEffects::BLINDNESS());
|
||||
$result->register("conduit_power", fn() => VanillaEffects::CONDUIT_POWER());
|
||||
$result->register("darkness", fn() => VanillaEffects::DARKNESS());
|
||||
$result->register("fatal_poison", fn() => VanillaEffects::FATAL_POISON());
|
||||
$result->register("fire_resistance", fn() => VanillaEffects::FIRE_RESISTANCE());
|
||||
$result->register("haste", fn() => VanillaEffects::HASTE());
|
||||
|
@ -36,6 +36,7 @@ use pocketmine\utils\RegistryTrait;
|
||||
* @method static AbsorptionEffect ABSORPTION()
|
||||
* @method static Effect BLINDNESS()
|
||||
* @method static Effect CONDUIT_POWER()
|
||||
* @method static Effect DARKNESS()
|
||||
* @method static PoisonEffect FATAL_POISON()
|
||||
* @method static Effect FIRE_RESISTANCE()
|
||||
* @method static Effect HASTE()
|
||||
@ -68,6 +69,7 @@ final class VanillaEffects{
|
||||
//TODO: bad_omen
|
||||
self::register("blindness", new Effect(KnownTranslationFactory::potion_blindness(), new Color(0x1f, 0x1f, 0x23), true));
|
||||
self::register("conduit_power", new Effect(KnownTranslationFactory::potion_conduitPower(), new Color(0x1d, 0xc2, 0xd1)));
|
||||
self::register("darkness", new Effect(KnownTranslationFactory::effect_darkness(), new Color(0x29, 0x27, 0x21), true, 600, false));
|
||||
self::register("fatal_poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true, 600, true, true));
|
||||
self::register("fire_resistance", new Effect(KnownTranslationFactory::potion_fireResistance(), new Color(0xe4, 0x9a, 0x3a)));
|
||||
self::register("haste", new Effect(KnownTranslationFactory::potion_digSpeed(), new Color(0xd9, 0xc0, 0x43)));
|
||||
|
@ -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{
|
||||
|
32
src/event/block/BlockDeathEvent.php
Normal file
32
src/event/block/BlockDeathEvent.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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\event\block;
|
||||
|
||||
/**
|
||||
* Called when a block dies.
|
||||
* This could be things like corals dying due to no water being nearby.
|
||||
*/
|
||||
class BlockDeathEvent extends BaseBlockChangeEvent{
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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.
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -43,6 +43,34 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ACCEPT_LICENSE, []);
|
||||
}
|
||||
|
||||
public static function action_interact_armorstand_equip() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_ARMORSTAND_EQUIP, []);
|
||||
}
|
||||
|
||||
public static function action_interact_armorstand_pose() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_ARMORSTAND_POSE, []);
|
||||
}
|
||||
|
||||
public static function action_interact_exit_boat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_EXIT_BOAT, []);
|
||||
}
|
||||
|
||||
public static function action_interact_fishing() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_FISHING, []);
|
||||
}
|
||||
|
||||
public static function action_interact_name() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_NAME, []);
|
||||
}
|
||||
|
||||
public static function action_interact_ride_boat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_RIDE_BOAT, []);
|
||||
}
|
||||
|
||||
public static function action_interact_ride_minecart() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_RIDE_MINECART, []);
|
||||
}
|
||||
|
||||
public static function chat_type_achievement(Translatable|string $param0, Translatable|string $param1) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::CHAT_TYPE_ACHIEVEMENT, [
|
||||
0 => $param0,
|
||||
@ -627,6 +655,12 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_fallingBlock(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_FALLINGBLOCK, [
|
||||
0 => $param0,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_generic(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
|
||||
0 => $param0,
|
||||
@ -691,6 +725,13 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_trident(Translatable|string $param0, Translatable|string $param1) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_TRIDENT, [
|
||||
0 => $param0,
|
||||
1 => $param1,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_wither(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_WITHER, [
|
||||
0 => $param0,
|
||||
@ -863,6 +904,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_SOUL_SPEED, []);
|
||||
}
|
||||
|
||||
public static function enchantment_swift_sneak() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_SWIFT_SNEAK, []);
|
||||
}
|
||||
|
||||
public static function enchantment_thorns() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_THORNS, []);
|
||||
}
|
||||
@ -954,6 +999,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_13_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_5_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_5_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_blocks_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_BLOCKS_DESC, []);
|
||||
}
|
||||
@ -978,6 +1027,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_MELLOHI_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_otherside_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_OTHERSIDE_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_pigstep_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_PIGSTEP_DESC, []);
|
||||
}
|
||||
|
@ -33,6 +33,13 @@ final class KnownTranslationKeys{
|
||||
public const ABILITY_FLIGHT = "ability.flight";
|
||||
public const ABILITY_NOCLIP = "ability.noclip";
|
||||
public const ACCEPT_LICENSE = "accept_license";
|
||||
public const ACTION_INTERACT_ARMORSTAND_EQUIP = "action.interact.armorstand.equip";
|
||||
public const ACTION_INTERACT_ARMORSTAND_POSE = "action.interact.armorstand.pose";
|
||||
public const ACTION_INTERACT_EXIT_BOAT = "action.interact.exit.boat";
|
||||
public const ACTION_INTERACT_FISHING = "action.interact.fishing";
|
||||
public const ACTION_INTERACT_NAME = "action.interact.name";
|
||||
public const ACTION_INTERACT_RIDE_BOAT = "action.interact.ride.boat";
|
||||
public const ACTION_INTERACT_RIDE_MINECART = "action.interact.ride.minecart";
|
||||
public const CHAT_TYPE_ACHIEVEMENT = "chat.type.achievement";
|
||||
public const CHAT_TYPE_ADMIN = "chat.type.admin";
|
||||
public const CHAT_TYPE_ANNOUNCEMENT = "chat.type.announcement";
|
||||
@ -138,6 +145,7 @@ final class KnownTranslationKeys{
|
||||
public const DEATH_ATTACK_EXPLOSION = "death.attack.explosion";
|
||||
public const DEATH_ATTACK_EXPLOSION_PLAYER = "death.attack.explosion.player";
|
||||
public const DEATH_ATTACK_FALL = "death.attack.fall";
|
||||
public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
|
||||
public const DEATH_ATTACK_GENERIC = "death.attack.generic";
|
||||
public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
|
||||
public const DEATH_ATTACK_INWALL = "death.attack.inWall";
|
||||
@ -148,6 +156,7 @@ final class KnownTranslationKeys{
|
||||
public const DEATH_ATTACK_OUTOFWORLD = "death.attack.outOfWorld";
|
||||
public const DEATH_ATTACK_PLAYER = "death.attack.player";
|
||||
public const DEATH_ATTACK_PLAYER_ITEM = "death.attack.player.item";
|
||||
public const DEATH_ATTACK_TRIDENT = "death.attack.trident";
|
||||
public const DEATH_ATTACK_WITHER = "death.attack.wither";
|
||||
public const DEATH_FELL_ACCIDENT_GENERIC = "death.fell.accident.generic";
|
||||
public const DEFAULT_GAMEMODE = "default_gamemode";
|
||||
@ -190,6 +199,7 @@ final class KnownTranslationKeys{
|
||||
public const ENCHANTMENT_PROTECT_FIRE = "enchantment.protect.fire";
|
||||
public const ENCHANTMENT_PROTECT_PROJECTILE = "enchantment.protect.projectile";
|
||||
public const ENCHANTMENT_SOUL_SPEED = "enchantment.soul_speed";
|
||||
public const ENCHANTMENT_SWIFT_SNEAK = "enchantment.swift_sneak";
|
||||
public const ENCHANTMENT_THORNS = "enchantment.thorns";
|
||||
public const ENCHANTMENT_TRIDENTCHANNELING = "enchantment.tridentChanneling";
|
||||
public const ENCHANTMENT_TRIDENTIMPALING = "enchantment.tridentImpaling";
|
||||
@ -211,12 +221,14 @@ final class KnownTranslationKeys{
|
||||
public const IP_WARNING = "ip_warning";
|
||||
public const ITEM_RECORD_11_DESC = "item.record_11.desc";
|
||||
public const ITEM_RECORD_13_DESC = "item.record_13.desc";
|
||||
public const ITEM_RECORD_5_DESC = "item.record_5.desc";
|
||||
public const ITEM_RECORD_BLOCKS_DESC = "item.record_blocks.desc";
|
||||
public const ITEM_RECORD_CAT_DESC = "item.record_cat.desc";
|
||||
public const ITEM_RECORD_CHIRP_DESC = "item.record_chirp.desc";
|
||||
public const ITEM_RECORD_FAR_DESC = "item.record_far.desc";
|
||||
public const ITEM_RECORD_MALL_DESC = "item.record_mall.desc";
|
||||
public const ITEM_RECORD_MELLOHI_DESC = "item.record_mellohi.desc";
|
||||
public const ITEM_RECORD_OTHERSIDE_DESC = "item.record_otherside.desc";
|
||||
public const ITEM_RECORD_PIGSTEP_DESC = "item.record_pigstep.desc";
|
||||
public const ITEM_RECORD_STAL_DESC = "item.record_stal.desc";
|
||||
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
|
||||
|
@ -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;
|
||||
@ -173,6 +173,14 @@ class Language{
|
||||
return $this->internalGet($id) ?? $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return array<string, string>
|
||||
*/
|
||||
public function getAll() : array{
|
||||
return $this->lang;
|
||||
}
|
||||
|
||||
protected function parseTranslation(string $text, ?string $onlyPrefix = null) : string{
|
||||
$newString = "";
|
||||
|
||||
|
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;
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user