Compare commits

..

85 Commits

Author SHA1 Message Date
bf7014e0ec Release 4.11.0-BETA1 2022-11-07 15:18:05 +00:00
824ed0a56a Merge branch 'stable' into next-minor 2022-11-07 15:04:48 +00:00
b3ccf41307 4.10.2 is next 2022-11-07 14:50:44 +00:00
a39938e6b6 Release 4.10.1 2022-11-07 14:50:43 +00:00
d5bf88acc0 Added missing changelog items for 4.10.0 release
these really ought to have been released in 4.9.2, but it's too late for that now.
2022-11-07 14:43:11 +00:00
2d0602d19f World: fixed spawning in the void when the spawn terrain is higher than y=70 on default worlds
fixes #5390
2022-11-07 14:24:17 +00:00
3a2a23b236 Fixed totem activating when having 1HP instead of 0HP (#5380) 2022-11-07 13:54:14 +00:00
1a8c8af523 Bump shivammathur/setup-php from 2.21.2 to 2.22.0 (#5375)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.21.2 to 2.22.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.21.2...2.22.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-07 13:40:53 +00:00
1e9d83f014 BaseInventory: validate given array in setContents() (#5391) 2022-11-07 13:09:05 +00:00
6153a2ac70 Merge remote-tracking branch 'origin/stable' into next-minor 2022-11-04 20:51:40 +00:00
ed452b9ccd Scrub PHPStan baselines 2022-11-04 20:51:22 +00:00
c19880e045 bootstrap: fix PHPStan error 2022-11-04 20:50:27 +00:00
cdbdcb5d67 Merge branch 'stable' into next-minor 2022-11-04 20:44:28 +00:00
29301614e8 Remove dead comments (#5389) 2022-11-04 20:23:56 +00:00
2fdc46c165 PHPStan 1.9 features 2022-11-04 20:23:34 +00:00
bfd1b2c635 PHPStan 1.9.1 2022-11-04 18:28:07 +00:00
1671405cd0 Merge branch 'stable' into next-minor 2022-11-02 16:03:30 +00:00
fe982c697b Durable: reset durability when overstacked items are broken
this is not really defined behaviour, but it makes more sense than the current behaviour, which makes the tool unbreakable unintentionally.

fixes #5378
2022-11-02 15:42:44 +00:00
1572b31b8d FormattedCommandAlias: do not fill empty strings for missing placeholder arguments
this is pretty much always going to cause unexpected behaviour, as most execute() implementations don't expect empty strings, and it can also pad the args with dummy entries, breaking argument count requirements (e.g. aliasing say  and using the alias with no arguments will confuse the target command).

Instead:
- Drop arguments that cannot be resolved (leave them unspecified)
- If they are at the end of the argument sequence, this is OK - it will behave the same as if some optional arguments weren't specified.
- If they are in the middle of the argument sequence, this will generate an error - this is preferable to having the target invoked with an empty string, which might cause unexpected behaviour.

fixes #5379
2022-11-02 15:02:29 +00:00
b6f6671a81 Merge branch 'stable' into next-minor 2022-10-31 15:34:24 +00:00
6da467b142 Updated composer dependencies 2022-10-31 15:27:56 +00:00
44af519cd6 SpawnResponsePacketHandler: silence PlayerAuthInputPacket debug spam (#5368) 2022-10-30 19:24:26 +00:00
fb31e6085e StatusCommand: fix condition order for TPS colour (#5366) 2022-10-26 23:26:55 +01:00
e4548da173 4.10.1 is next 2022-10-26 01:19:08 +01:00
9a0ead6deb Validate paths in --data and --plugins
closes #2861
2022-10-21 14:28:22 +01:00
7864294336 Merge branch 'stable' into next-minor 2022-10-16 16:53:14 +01:00
b74c092d9b Server: removed reference to nonexistent API method 2022-10-15 15:06:43 +01:00
7bcc663b60 Migrate core code to using symfony/filesystem
webmozart/path-util is retained for plugin compatibility, but is dropped in 5.0
2022-10-14 21:51:29 +01:00
b3bda788d9 Server: Deprecated getPlayerByPrefix()
this is only used for commands anyway, but we can't get rid of it yet.
2022-10-14 21:45:09 +01:00
2cc8a56e68 Server: fixed borked serverCommand timings 2022-10-14 20:02:44 +01:00
57deb60355 Merge branch 'stable' into next-minor 2022-10-13 21:03:50 +01:00
6ae7cb288e Merge remote-tracking branch 'origin/stable' into next-minor 2022-10-11 21:59:40 +01:00
3feaa18f6c DelegateInventory: use WeakReference and __destruct to clean up inventory listener
this is more sane, since it allows the delegate to be reused without unexpected behaviour.
2022-09-30 15:12:37 +01:00
41970feb57 Entity: Fire EntitySpawnEvent/ItemSpawnEvent on the first entity tick, instead of in the constructor (#5314)
This allows plugins to modify the entity via setters in EntitySpawnEvent without their changes getting overwritten by setter calls directly after the 'new YourEntity' statement.

As well as benefiting plugins, this also clears a path for a BC-breaking change in PM5 (to have the programmer use addEntity() to spawn entities, instead of the constructor doing it, which will improve on a number of data handling aspects).

fixes #4973

This targets next-minor because it has some side effects on plugins that depended on the old behaviour, such as VanillaHopper, so it's not suitable for a patch release.
2022-09-29 22:30:12 +01:00
0c7f8470b9 Avoid repeated strtolower usages in a couple of places 2022-09-28 21:30:06 +01:00
d6bbf8217d ResourcePackManager: avoid repeated operation 2022-09-28 20:57:17 +01:00
bda0ca23b4 Living: deprecated hasLineOfSight() 2022-09-28 17:34:51 +01:00
b21cd82e94 Allow specifying a key for encrypted resource packs (#5297) 2022-09-28 17:27:33 +01:00
1c7b1e9e5d Fix sugarcane behaviour on fertilizers (#4930) 2022-09-28 16:38:24 +01:00
b87e4d8bd3 Introduce and use TextFormat::addBase() (#5268)
This function adds "base" format to a string. The given formats are inserted directly after any RESET code in the sequence.

An example of where this is needed is in the logger.

Without this change, the following code:
$logger->notice("I'm a " . TextFormat::RED . "special" . TextFormat::RESET . " cookie");

causes the "cookie" part of the message to show as grey, instead of the expected aqua for NOTICE level messages.

There are also many workarounds for this problem throughout the server, mostly in command outputs, being forced to use WHITE instead of RESET to avoid breaking the logger output.
2022-09-28 16:13:11 +01:00
86a2f8e360 Merge branch 'stable' into next-minor 2022-09-28 01:01:51 +01:00
cfb0cad7e0 Console commands now write to stdout directly, instead of being fed through the logger
this has a number of implications:
- Console command outputs are now (obviously) not logged. This is consistent with every other type of command sender, be it RCON, players, or anything else.
- The assumption that the console command sender must be able to see the logger output is now broken, since the command sender can receive output separately from the logs.

In the future, it might be desirable to send the console command output to stderr instead of stdout, so that stdout can be silenced while still allowing commands to be used.

closes #2543
2022-09-27 21:03:03 +01:00
83e5b0adb6 ConsoleCommandSender is no longer responsible for forwarding broadcast messages to the logger (#5311)
This is a step towards implementing #2543.
2022-09-27 20:58:33 +01:00
a7dfa0907c Merge branch 'stable' into next-minor 2022-09-24 18:07:56 +01:00
83a136a176 EntityFactory: Avoid code duplication on validation of creation functions (#5294) 2022-09-24 13:55:24 +01:00
e4fc523251 Introduce Player::sendToastNotification() (#5102) 2022-09-23 11:37:08 +01:00
7d29ac8293 Merge branch 'stable' into next-minor 2022-09-21 14:21:27 +01:00
89e29448ee Merge branch 'stable' into next-minor 2022-09-20 20:18:06 +01:00
9f97654f6f Update InstantDamage and Regeneration values (#5279) 2022-09-15 20:26:37 +01:00
441b06f6c7 Merge branch 'stable' into next-minor 2022-09-15 12:44:53 +01:00
ffb3af3e0d fix CS 2022-09-02 00:59:58 +01:00
b3f03d7ae6 Poll console on the main thread, instead of using a separate thread
There's no need to use an extra thread for this, since there's no concern of a socket getting stuck in a blocking read.

This is one less thing that can go wrong because of pthreads.
2022-09-02 00:58:49 +01:00
2585160ca2 ConsoleReaderChildProcess: Commit suicide if the parent process dies and doesn't clean up
This happens if the main server process was forcibly killed, e.g. by the kill command on Linux, or taskkill/TaskManager on Windows.

Previously, the process would stick around as a zombie, which messed up terminals in some cases (e.g. git bash), though even having zombies with no side effects is bad enough.
2022-09-02 00:25:31 +01:00
14d3e6c7d5 Allow disabling the console reader via pocketmine.yml
Useful to save resources on headless servers where the console is never used (e.g. hosted server, Docker, etc.)
2022-09-01 23:43:54 +01:00
65ec318c30 PluginManager: Ensure dependents are disabled before dependencies in disablePlugins() (#5227)
this could later be expanded to disablePlugin() to make this disable order mandatory, to provide certainty for plugin devs.

Alternative solutions to this include disabling plugins in the opposite order that they were enabled in, but this doesn't allow for random plugin disables. This way seemed to make sense.
2022-08-31 18:43:30 +01:00
a25cb3741a Limit valid supporting blocks of dead bush (#5055) 2022-08-31 02:18:18 +01:00
5b89833d5c Merge branch 'stable' into next-minor 2022-08-27 17:27:15 +01:00
c16893cbac Merge branch 'stable' into next-minor 2022-08-25 19:25:19 +01:00
5d5366a7c8 Merge branch 'stable' into next-minor 2022-08-21 19:11:59 +01:00
d6af2b12f4 Merge branch 'complex-inventory-rewrite' into next-minor 2022-08-18 17:30:00 +01:00
ad2d59923c Merge branch 'stable' into next-minor 2022-08-18 17:06:15 +01:00
792c1b62b7 Introduce and use TransactionBuilder for inventory evacuations 2022-08-18 17:04:12 +01:00
e90abecf38 Rewrite InventoryManager handling of complex mapped inventories
this turned out to be necessary when dealing with the ItemStackRequest system.
2022-08-17 14:42:30 +01:00
4d34885b15 Merge branch 'stable' into next-minor 2022-08-14 20:11:10 +01:00
f7ab0a3b92 Merge branch 'stable' into next-minor 2022-08-14 18:37:56 +01:00
16ed16722a Merge branch 'stable' into next-minor 2022-07-24 21:21:37 +01:00
42f9336f7a Split packet receive timings into decode and handle subcomponents 2022-07-24 21:16:52 +01:00
2940547026 Eliminate repeated calls to Position->getWorld()
as well as improving readability, it also improves performance in some areas.
2022-07-20 20:44:05 +01:00
24e72ec109 ContainerTrait: improve performance of block destroy hook
this was creating useless vector3s for every iteration, as well as repeatedly invoking Position->getWorld() for no reason.
2022-07-20 20:39:37 +01:00
dbc0b9634b Merge branch 'stable' into next-minor 2022-07-19 20:35:47 +01:00
040516054f Merge branch 'stable' into next-minor 2022-07-19 20:20:10 +01:00
4e3964ffce Armor: added clearCustomColor() 2022-07-16 15:10:07 +01:00
93254523e6 Merge branch 'stable' into next-minor 2022-07-14 22:04:38 +01:00
e00f8e3a32 Merge branch 'stable' into next-minor 2022-07-14 20:59:06 +01:00
e2855aadff Simplify handling of broken transactions for crafting and friends
this allows stuff like smithing tables to work without needing any extra hacks.

Implementing enchanting or anvils would require some extra work, but I don't plan to implement those under the legacy transaction system anyway.
2022-07-14 20:50:06 +01:00
4d6ec66270 Merge branch 'stable' into next-minor 2022-07-14 19:54:38 +01:00
f1a63098bd Merge branch 'stable' into next-minor 2022-07-14 16:06:15 +01:00
4b1052022c Merge branch 'stable' into next-minor 2022-07-13 17:00:58 +01:00
5a8983dd81 Merge branch 'stable' into next-minor 2022-07-06 16:17:04 +01:00
38651fde74 Merge branch 'stable' into next-minor 2022-06-29 16:53:31 +01:00
63ee03a7be Merge branch 'stable' into next-minor 2022-06-24 01:40:39 +01:00
088a2e478c Merge branch 'stable' into next-minor 2022-06-24 01:32:53 +01:00
35fd71eddf Fix CS in VersionInfo
this got overlooked because I just nuked the changes coming in from stable and didn't notice the formatting changes.
2022-06-07 20:26:02 +01:00
be168beba0 Merge branch 'stable' into next-minor 2022-06-07 19:55:58 +01:00
4b73bedd57 Bump version to 4.6.0+dev 2022-06-01 19:45:20 +01:00
134 changed files with 1502 additions and 495 deletions

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.21.2
uses: shivammathur/setup-php@2.22.0
with:
php-version: 8.0

View File

@ -18,7 +18,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.21.2
uses: shivammathur/setup-php@2.22.0
with:
php-version: 8.0

View File

@ -195,7 +195,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.21.2
uses: shivammathur/setup-php@2.22.0
with:
php-version: 8.0
tools: php-cs-fixer:3.11

View File

@ -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;

View File

@ -11,4 +11,28 @@ Released 26th October 2022.
## General
- Added support for Minecraft: Bedrock Edition 1.19.40.
- Removed support for older versions.
- Removed support for older versions.
## Fixes
- Fixed incorrect command descriptions showing in `/help` when multiple commands use the same name. Previously, the most recently registered command would show, even though it wouldn't actually be invoked.
- Fixed splash potions affecting players in spectator mode.
- Fixed `World->addParticle()` sending particles to players who couldn't possibly see them when a list of targets was used.
- Fixed `World->addSound()` sending sounds to players who couldn't possibly hear them when a list of targets was used.
## Documentation
- Improved type information available for various API methods in `World`.
# 4.10.1
Released 7th November 2022.
## Fixes
- Fixed spawning in the void if spawn terrain in a world is solid at the default spawn position.
- Fixed totems of undying activating when the player has 1 HP remaining.
- Fixed durable items such as tools becoming unbreakable when in stacks larger than 1. Now, the durability correctly resets when the tool breaks.
- TPS below 12 now correctly shows as red in `/status`. Previously, it showed as orange due to a condition ordering bug.
- Improved handling of missing arguments in user-defined `pocketmine.yml` command aliases. Previously, missing arguments would be filled with an empty string, which caused a variety of unexpected behaviour.
## Internals
- Added validation for the array given to `BaseInventory->setContents()` to ensure that it contains only `Item` instances.
- Silenced `PlayerAuthInputPacket` spam when the session is in the "spawn response" state.
- Updated to PHPStan 1.9.

67
changelogs/4.11-beta.md Normal file
View File

@ -0,0 +1,67 @@
**For Minecraft: Bedrock Edition 1.19.40**
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 4.11.0-BETA1
Released 7th November 2022.
## General
- Packet receive timings have now been split into two subcategories - Decode and Handle.
- Console command entry can now be disabled via the `console.enable-input` setting in `pocketmine.yml`.
- Best suited for headless servers (e.g. in a Docker container) where the console will never be used anyway.
- Disabling the console reader slightly reduces memory usage, because console reading currently requires an additional subprocess.
- Console command output now appears on the terminal only, and is not written to the log file.
- The output from console commands now appears with a `Command output |` prefix, instead of as a log message.
- Introduced validation for the `--data` and `--plugins` command line options.
- Encrypted resource packs are now supported, by means of adding a `.key` file alongside the pack in the `resource_packs` folder.
- e.g. `MyEncryptedPack.zip` -> `MyEncryptedPack.zip.key`
## Gameplay
- Fixed supporting blocks of dead bush to be in line with vanilla.
- Sugarcane can now be grown using bonemeal on any part of the sugarcane. Previously, it only worked when used on the bottom block.
- Fixed modifier values for Instant Damage and Regeneration effects.
## API
### General
- Plugins are now always disabled before their dependencies, to ensure that they are able to shutdown properly (e.g. a core plugin depending on a database plugin may want to save data to a DB during `onDisable()`).
- [`webmozart/path-util`](https://packagist.org/packages/webmozart/path-util) has been deprecated, and will be dropped in favour of [`symfony/filesystem`](https://packagist.org/packages/symfony/filesystem) in PM5.
- To prepare for this change, simply replace any usage of `Webmozart\PathUtil\Path` with `Symfony\Component\Filesystem\Path`, which is available as a dependency in this release.
### `pocketmine`
- The following API methods are now deprecated:
- `Server->getPlayerByPrefix()`
### `pocketmine\entity`
- `EntitySpawnEvent` and `ItemSpawnEvent` are now fired on the first tick after the entity is added to the world. Previously, these events were called directly from the entity constructor, making it impossible to get properties like velocity which are often set after the entity is created.
- The following API methods are now deprecated:
- `Living->hasLineOfSight()`
### `pocketmine\item`
- The following new API methods have been added:
- `public Armor->clearCustomColor() : $this`
### `pocketmine\inventory\transaction`
- Introduced a `TransactionBuilder` class. This makes it less of a hassle to build an `InventoryTransaction` server-side, since the regular `Inventory` API methods can be used, rather than having to manually create `SlotChangeAction`s.
### `pocketmine\player`
- The following new API methods have been added:
- `public Player->sendToastNotification(string $title, string $body) : void` - makes a grey box appear at the top of the player's screen containing the specified message
### `pocketmine\utils`
- The following new API methods have been added:
- `public static TextFormat::addBase(string $baseFormat, string $string) : string` - used for coloured log messages, changes the base formatting of a string by inserting the given formatting codes after every RESET code
## Internals
- Improved performance of `ContainerTrait` dropping items on block destroy. (24e72ec109c1442b09558df89b6833cf2f2e0ec7)
- Avoid repeated calls to `Position->getWorld()` (use local variables). (2940547026db40ce76deb46e992870de3ead79ad)
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. (e90abecf38d9c57635fa0497514bba7e546a2469)
- Console polling is now done on the main thread (no longer a performance concern).
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie.
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged.
- `DelegateInventory` now uses `WeakReference` to track its inventory listener. This allows the delegate to be reused.

View File

@ -41,7 +41,7 @@
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.6.0",
"pocketmine/locale-data": "~2.8.0 <2.8.9",
"pocketmine/locale-data": "~2.9.0",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
@ -50,10 +50,11 @@
"pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0",
"ramsey/uuid": "^4.1",
"symfony/filesystem": "^5.4",
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.8.11",
"phpstan/phpstan": "1.9.1",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"

376
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ed062ef1dc3113ad2a75ba4d4d5e174f",
"content-hash": "b535dc262141936698680b92902f8e28",
"packages": [
{
"name": "adhocore/json-comment",
@ -536,16 +536,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.8.7",
"version": "2.9.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "e115d3d64a508065f1cedad1be55528906308456"
"reference": "8813ffd2a4501521ca9433c534f3009f941de136"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/e115d3d64a508065f1cedad1be55528906308456",
"reference": "e115d3d64a508065f1cedad1be55528906308456",
"url": "https://api.github.com/repos/pmmp/Language/zipball/8813ffd2a4501521ca9433c534f3009f941de136",
"reference": "8813ffd2a4501521ca9433c534f3009f941de136",
"shasum": ""
},
"type": "library",
@ -553,9 +553,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.8.7"
"source": "https://github.com/pmmp/Language/tree/2.9.2"
},
"time": "2022-08-21T20:37:16+00:00"
"time": "2022-10-21T20:30:38+00:00"
},
{
"name": "pocketmine/log",
@ -1022,6 +1022,318 @@
],
"time": "2022-09-16T03:22:46+00:00"
},
{
"name": "symfony/filesystem",
"version": "v5.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8",
"symfony/polyfill-php80": "^1.16"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.4.13"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-09-21T19:53:16+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-10T07:21:04+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.26.0",
@ -1510,16 +1822,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.8.11",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "46e223dd68a620da18855c23046ddb00940b4014"
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014",
"reference": "46e223dd68a620da18855c23046ddb00940b4014",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f",
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f",
"shasum": ""
},
"require": {
@ -1549,7 +1861,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.8.11"
"source": "https://github.com/phpstan/phpstan/tree/1.9.1"
},
"funding": [
{
@ -1565,25 +1877,25 @@
"type": "tidelift"
}
],
"time": "2022-10-24T15:45:13+00:00"
"time": "2022-11-04T13:35:59+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.1.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "4a3c437c09075736285d1cabb5c75bf27ed0bc84"
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/4a3c437c09075736285d1cabb5c75bf27ed0bc84",
"reference": "4a3c437c09075736285d1cabb5c75bf27ed0bc84",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/dea1f87344c6964c607d9076dee42d891f3923f0",
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.5.0"
"phpstan/phpstan": "^1.8.11"
},
"conflict": {
"phpunit/phpunit": "<7.0"
@ -1615,9 +1927,9 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.1.1"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.2.2"
},
"time": "2022-04-20T15:24:25+00:00"
"time": "2022-10-28T10:23:07+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
@ -1669,16 +1981,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.17",
"version": "9.2.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a",
"shasum": ""
},
"require": {
@ -1734,7 +2046,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18"
},
"funding": [
{
@ -1742,7 +2054,7 @@
"type": "github"
}
],
"time": "2022-08-30T12:24:04+00:00"
"time": "2022-10-27T13:35:33+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1987,16 +2299,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.25",
"version": "9.5.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d"
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
"reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"shasum": ""
},
"require": {
@ -2069,7 +2381,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
},
"funding": [
{
@ -2085,7 +2397,7 @@
"type": "tidelift"
}
],
"time": "2022-09-25T03:44:45+00:00"
"time": "2022-10-28T06:00:21+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -168,6 +168,9 @@ timings:
host: timings.pmmp.io
console:
#Whether to accept commands via the console. If disabled, anything typed on the console will be ignored.
#Useful to save resources on headless servers where the console is never used (e.g. hosted server, Docker, etc.)
enable-input: true
#Choose whether to enable server stats reporting on the console title.
#NOTE: The title ticker will be disabled regardless if console colours are not enabled.
title-tick: true

View File

@ -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

View File

@ -30,7 +30,7 @@ use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\timings\Timings;
use pocketmine\utils\Process;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
use function arsort;
use function count;
use function fclose;
@ -398,7 +398,7 @@ class MemoryManager{
do{
$continue = false;
foreach($objects as $hash => $object){
foreach(Utils::stringifyKeys($objects) as $hash => $object){
if(!is_object($object)){
continue;
}
@ -483,6 +483,11 @@ class MemoryManager{
* @param object[] $objects reference parameter
* @param int[] $refCounts reference parameter
*
* @phpstan-param array<string, object> $objects
* @phpstan-param array<string, int> $refCounts
* @phpstan-param-out array<string, object> $objects
* @phpstan-param-out array<string, int> $refCounts
*
* @return mixed
*/
private static function continueDump($from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){

View File

@ -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"])){

View File

@ -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);

View File

@ -31,9 +31,9 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.10.0";
public const BASE_VERSION = "4.11.0-BETA1";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";
public const BUILD_CHANNEL = "beta";
private function __construct(){
//NOOP

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}

View File

@ -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];

View File

@ -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)));
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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;
}

View File

@ -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());
}
}
}

View File

@ -151,11 +151,12 @@ class ItemFrame extends Flowable{
if($this->framedItem === null){
return false;
}
$world = $this->position->getWorld();
if(lcg_value() <= $this->itemDropChance){
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
}
$this->setFramedItem(null);
$this->position->getWorld()->setBlock($this->position, $this);
$world->setBlock($this->position, $this);
return true;
}

View File

@ -84,6 +84,7 @@ class Leaves extends Transparent{
/**
* @param true[] $visited reference parameter
* @phpstan-param array<int, true> $visited
* @phpstan-param-out array<int, true> $visited
*/
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
$index = World::blockHash($pos->x, $pos->y, $pos->z);
@ -123,11 +124,12 @@ class Leaves extends Transparent{
if(!$this->noDecay && $this->checkDecay){
$ev = new LeavesDecayEvent($this);
$ev->call();
$world = $this->position->getWorld();
if($ev->isCancelled() || $this->findLog($this->position)){
$this->checkDecay = false;
$this->position->getWorld()->setBlock($this->position, $this, false);
$world->setBlock($this->position, $this, false);
}else{
$this->position->getWorld()->useBreakOn($this->position);
$world->useBreakOn($this->position);
}
}
}

View File

@ -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;
}

View File

@ -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()
);

View File

@ -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;
}

View File

@ -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());
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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));

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -28,9 +28,9 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
use function array_map;
use function array_shift;
use function count;
use function implode;
use function preg_match;
use function strlen;
use function strpos;
@ -62,7 +62,20 @@ class FormattedCommandAlias extends Command{
foreach($this->formatStrings as $formatString){
try{
$formatArgs = CommandStringHelper::parseQuoteAware($formatString);
$commands[] = array_map(fn(string $formatArg) => $this->buildCommand($formatArg, $args), $formatArgs);
$unresolved = [];
$processedArgs = [];
foreach($formatArgs as $formatArg){
$processedArg = $this->buildCommand($formatArg, $args);
if($processedArg === null){
$unresolved[] = $formatArg;
}elseif(count($unresolved) !== 0){
//unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
}else{
$processedArgs[] = $processedArg;
}
}
$commands[] = $processedArgs;
}catch(\InvalidArgumentException $e){
$sender->sendMessage(TextFormat::RED . $e->getMessage());
return false;
@ -107,7 +120,7 @@ class FormattedCommandAlias extends Command{
/**
* @param string[] $args
*/
private function buildCommand(string $formatString, array $args) : string{
private function buildCommand(string $formatString, array $args) : ?string{
$index = 0;
while(($index = strpos($formatString, '$', $index)) !== false){
$start = $index;
@ -129,6 +142,9 @@ class FormattedCommandAlias extends Command{
}
$replacement = self::buildReplacement($args, $position, $rest);
if($replacement === null){
return null;
}
$end = $index + strlen($fullPlaceholder);
$formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
@ -143,9 +159,9 @@ class FormattedCommandAlias extends Command{
* @param string[] $args
* @phpstan-param list<string> $args
*/
private static function buildReplacement(array $args, int $position, bool $rest) : string{
$replacement = "";
private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
if($rest && $position < count($args)){
$replacement = "";
for($i = $position, $c = count($args); $i < $c; ++$i){
if($i !== $position){
$replacement .= " ";
@ -153,11 +169,13 @@ class FormattedCommandAlias extends Command{
$replacement .= $args[$i];
}
return $replacement;
}elseif($position < count($args)){
$replacement .= $args[$position];
return $args[$position];
}
return $replacement;
return null;
}
/**

View File

@ -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]);
}
}

View File

@ -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{

View File

@ -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));

View File

@ -76,7 +76,6 @@ class HelpCommand extends VanillaCommand{
$pageHeight = $sender->getScreenLineHeight();
if($commandName === ""){
/** @var Command[][] $commands */
$commands = [];
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
if($command->testPermissionSilent($sender)){
@ -95,7 +94,7 @@ class HelpCommand extends VanillaCommand{
foreach($commands[$pageNumber - 1] as $command){
$description = $command->getDescription();
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::WHITE . $descriptionString);
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
}
}
@ -107,18 +106,18 @@ class HelpCommand extends VanillaCommand{
$description = $cmd->getDescription();
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
->format(TextFormat::YELLOW . "--------- " . TextFormat::WHITE, TextFormat::YELLOW . " ---------"));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::WHITE . $descriptionString)
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
->prefix(TextFormat::GOLD));
$usage = $cmd->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $usageString)))
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
->prefix(TextFormat::GOLD));
$aliases = $cmd->getAliases();
sort($aliases, SORT_NATURAL);
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::WHITE . implode(", ", $aliases))
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
->prefix(TextFormat::GOLD));
return true;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -52,7 +52,7 @@ class StatusCommand extends VanillaCommand{
$mUsage = Process::getAdvancedMemoryUsage();
$server = $sender->getServer();
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::RESET . "Server status" . TextFormat::GREEN . " ----");
$time = (int) (microtime(true) - $server->getStartTime());
@ -82,10 +82,10 @@ class StatusCommand extends VanillaCommand{
$sender->sendMessage(TextFormat::GOLD . "Uptime: " . TextFormat::RED . $uptime);
$tpsColor = TextFormat::GREEN;
if($server->getTicksPerSecond() < 17){
$tpsColor = TextFormat::GOLD;
}elseif($server->getTicksPerSecond() < 12){
if($server->getTicksPerSecond() < 12){
$tpsColor = TextFormat::RED;
}elseif($server->getTicksPerSecond() < 17){
$tpsColor = TextFormat::GOLD;
}
$sender->sendMessage(TextFormat::GOLD . "Current TPS: {$tpsColor}{$server->getTicksPerSecond()} ({$server->getTickUsage()}%)");

View File

@ -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;

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
final class LegacyBiomeIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;

View File

@ -250,10 +250,8 @@ abstract class Entity{
$this->getWorld()->addEntity($this);
$this->lastUpdate = $this->server->getTick();
(new EntitySpawnEvent($this))->call();
$this->scheduleUpdate();
}
abstract protected function getInitialSizeInfo() : EntitySizeInfo;
@ -938,6 +936,14 @@ abstract class Entity{
return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize();
}
/**
* Called from onUpdate() on the first tick of a new entity. This is called before any movement processing or
* main ticking logic. Use this to fire any events related to spawning the entity.
*/
protected function onFirstUpdate(int $currentTick) : void{
(new EntitySpawnEvent($this))->call();
}
public function onUpdate(int $currentTick) : bool{
if($this->closed){
return false;
@ -954,6 +960,10 @@ abstract class Entity{
$this->lastUpdate = $currentTick;
if($this->justCreated){
$this->onFirstUpdate($currentTick);
}
if(!$this->isAlive()){
if($this->onDeathUpdate($tickDiff)){
$this->flagForDespawn();
@ -988,9 +998,7 @@ abstract class Entity{
$this->timings->stopTiming();
//if($this->isStatic())
return ($hasUpdate || $this->hasMovementUpdate());
//return !($this instanceof Player);
}
final public function scheduleUpdate() : void{

View File

@ -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;

View File

@ -356,7 +356,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
&& ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){
$compensation = $this->getHealth() - $source->getFinalDamage() - 1;
if($compensation < 0){
if($compensation <= -1){
$source->setModifier($compensation, EntityDamageEvent::MODIFIER_TOTEM);
}
}

View File

@ -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;

View File

@ -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){

View File

@ -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;

View File

@ -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{

View File

@ -27,6 +27,7 @@ use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\player\Player;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils;
use function array_slice;
use function count;
use function max;
@ -85,6 +86,7 @@ abstract class BaseInventory implements Inventory{
* @phpstan-param array<int, Item> $items
*/
public function setContents(array $items) : void{
Utils::validateArrayValueType($items, function(Item $item) : void{});
if(count($items) > $this->getSize()){
$items = array_slice($items, 0, $this->getSize(), true);
}

View File

@ -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;

View File

@ -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
}
}
}

View File

@ -134,6 +134,8 @@ class InventoryTransaction{
/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param-out Item[] $needItems
* @phpstan-param-out Item[] $haveItems
*
* @throws TransactionValidationException
*/

View 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;
}
}

View File

@ -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.

View File

@ -109,6 +109,7 @@ abstract class Durable extends Item{
*/
protected function onBroken() : void{
$this->pop();
$this->setDamage(0); //the stack size may be greater than 1 if overstacked by a plugin
}
/**

View File

@ -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");
}

View File

@ -743,6 +743,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL, []);
}
public static function effect_darkness() : Translatable{
return new Translatable(KnownTranslationKeys::EFFECT_DARKNESS, []);
}
public static function enchantment_arrowDamage() : Translatable{
return new Translatable(KnownTranslationKeys::ENCHANTMENT_ARROWDAMAGE, []);
}
@ -1393,6 +1397,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION, []);
}
public static function pocketmine_command_userDefined_description() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION, []);
}
public static function pocketmine_command_version_description() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_VERSION_DESCRIPTION, []);
}

View File

@ -160,6 +160,7 @@ final class KnownTranslationKeys{
public const DISCONNECTIONSCREEN_OUTDATEDSERVER = "disconnectionScreen.outdatedServer";
public const DISCONNECTIONSCREEN_RESOURCEPACK = "disconnectionScreen.resourcePack";
public const DISCONNECTIONSCREEN_SERVERFULL = "disconnectionScreen.serverFull";
public const EFFECT_DARKNESS = "effect.darkness";
public const ENCHANTMENT_ARROWDAMAGE = "enchantment.arrowDamage";
public const ENCHANTMENT_ARROWFIRE = "enchantment.arrowFire";
public const ENCHANTMENT_ARROWINFINITE = "enchantment.arrowInfinite";
@ -306,6 +307,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_COMMAND_TRANSFERSERVER_USAGE = "pocketmine.command.transferserver.usage";
public const POCKETMINE_COMMAND_UNBAN_IP_DESCRIPTION = "pocketmine.command.unban.ip.description";
public const POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION = "pocketmine.command.unban.player.description";
public const POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION = "pocketmine.command.userDefined.description";
public const POCKETMINE_COMMAND_VERSION_DESCRIPTION = "pocketmine.command.version.description";
public const POCKETMINE_COMMAND_VERSION_MINECRAFTVERSION = "pocketmine.command.version.minecraftVersion";
public const POCKETMINE_COMMAND_VERSION_NOSUCHPLUGIN = "pocketmine.command.version.noSuchPlugin";

View File

@ -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;

View 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;
}
}

View File

@ -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 ???
}

View File

@ -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){

View File

@ -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{

View File

@ -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;

View File

@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use Symfony\Component\Filesystem\Path;
use function array_key_exists;
use function file_get_contents;
use function is_array;
@ -169,6 +169,7 @@ final class ItemTranslator{
}
/**
* @phpstan-param-out bool $isComplexMapping
* @return int[]
* @phpstan-return array{int, int}
* @throws TypeConversionException

View File

@ -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;
/**

View File

@ -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");
}

View File

@ -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