Compare commits

...

36 Commits

Author SHA1 Message Date
3d790e3e4b Merge remote-tracking branch 'origin/minor-next' into compressor-threads 2023-11-16 09:50:19 +00:00
69f197dbec PluginBase: fixed erroneous replacement 2023-11-14 13:04:14 +00:00
13f34a500c PluginBase: clean up inconsistent getter vs property access usages 2023-11-14 12:59:38 +00:00
e5c96faa4b Server: clean up inconsistent getter vs property access usages 2023-11-14 12:59:05 +00:00
dd98e4aaed block: clean up unnecessary getter usages
with the assistance of a custom phpstan rule
this inconsistent mess has been bothering me for a long time
2023-11-14 12:47:33 +00:00
e92923aa10 Remove debug timings 2023-11-13 15:08:34 +00:00
d35d9e6ecf Enable compression-threads by default 2023-11-13 15:07:38 +00:00
671c65d787 Merge branch 'minor-next' into compressor-threads 2023-11-13 11:40:31 +00:00
e525699dd4 TimeTrackingSleeperHandler: record time spent in each Snooze handler 2023-11-13 11:35:39 +00:00
7e6550d05a Merge branch 'minor-next' into compressor-threads 2023-11-13 11:14:19 +00:00
923c922960 Merge branch 'stable' into minor-next 2023-11-13 11:13:12 +00:00
77590fb63a Server: fixed prepareBatch() not being marked as internal 2023-11-13 11:12:12 +00:00
366968722f CompressorWorker: isolate thread body inside static function
this prevents accidental access to shared properties, which would cost performance.
2023-11-13 10:50:33 +00:00
bd43ff6579 Update README.md
[ci skip]
2023-11-10 16:27:57 +00:00
c2189bc2df Update README.md
[ci skip]
2023-11-10 16:16:49 +00:00
58ea94bab8 ... 2023-11-10 15:41:17 +00:00
22b10e4cb0 Timings: Stop using BREAKDOWN group
with tree timings, the breakdown is actually pretty annoying, since it makes it hard to find a timer in the aggregate lists.
2023-11-10 15:36:35 +00:00
c44758f36c StringToItemParser: added pitcher_plant and pitcher_pod
it seems a bit weird to map pitcher_pod to PITCHER_CROP(). Perhaps this wasn't implemented correctly.
2023-11-10 15:26:02 +00:00
7a4cf8ef68 Prepare for PHP 8.2 as primary version 2023-11-09 19:04:53 +00:00
269b3d89a2 Update build/php submodule to pmmp/PHP-Binaries@39885cf248 2023-11-09 19:03:12 +00:00
b3766834c6 Merge branch 'stable' into minor-next 2023-11-09 19:02:08 +00:00
93699024da 5.8.3 is next 2023-11-09 18:51:20 +00:00
135fe149f0 Merge branch 'minor-next' into compressor-threads 2023-11-09 18:05:57 +00:00
50592dc269 Merge branch 'stable' into minor-next 2023-11-09 18:05:38 +00:00
94023b48e5 Added more timings 2023-11-09 14:55:26 +00:00
ff47da6675 Merge remote-tracking branch 'origin/minor-next' into compressor-threads 2023-11-09 14:41:24 +00:00
4103631bc1 Added Smithing Template items (#6132) 2023-11-09 14:25:49 +00:00
d09af2e30d World: don't assume that random Vector3 are int vectors
we can safely assume this for blocks (though the type info doesn't reflect it) but this is not safe to assume for random APIs that might be used by plugins.
2023-11-06 17:15:17 +00:00
bbe66e8e09 Block: Improve performance of encodeFullState()
if there's no state data to encode, we can avoid useless calls and object allocations.
For the best cases (blocks which don't use state data at all) this improves the performance of getStateId() by more than 10x.
Blocks which use one or the other benefit by a smaller but still significant margin.
2023-11-06 17:04:39 +00:00
457660235e Crops must have access to a light level of at least 9 2023-11-06 16:02:57 +00:00
d3b7861d1a Constify bootstrap options 2023-11-02 16:15:57 +00:00
a6b36d6c3c CropGrowthHelper: avoid unnecessary checks 2023-11-02 15:32:22 +00:00
109673382d Implemented modifiers for crop growth speed
closes #6070

there are some unresolved questions about the growth speed of beetroots, pitcher plants and torchflower crops, but that's a topic for another commit.
this change also doesn't account for the light levels.
2023-11-02 15:16:11 +00:00
0f9f0a3b7f Remove async compression pocketmine.yml options
CompressBatchTask is kept for internal testing for now
2023-06-05 21:36:33 +01:00
3e4d8f4a60 shush 2023-06-05 21:21:26 +01:00
20c2fae0c6 First look at specialized network compression threads
closes #5641

This has been mostly tested on Windows so far, where it offers substantial performance gains for compression.
On *nix, the performance advantage is smaller, since there's less overhead to creating new AsyncTask instances; however, the performance benefit is still clearly visible.

There are still some wrinkles to iron out, such as the dumb algorithm used for cycling through threads which wastes memory on small servers, but this change is mainly aimed at large servers, where the benefit will be clearly apparent.
In practice, this should reduce main thread CPU load by 10-20% on some of the largest servers, offering a large amount of headroom for increased player counts.
2023-06-05 21:11:13 +01:00
51 changed files with 876 additions and 267 deletions

View File

@ -15,7 +15,7 @@ jobs:
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.27.1
with:
php-version: 8.1
php-version: 8.2
- name: Restore Composer package cache
uses: actions/cache@v3

View File

@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: [8.1]
php-version: [8.2]
steps:
- uses: actions/checkout@v4

View File

@ -175,8 +175,8 @@ jobs:
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.27.1
with:
php-version: 8.1
tools: php-cs-fixer:3.17
php-version: 8.2
tools: php-cs-fixer:3.38
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -2,7 +2,7 @@
## Pre-requisites
- A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell
- PHP 8.1 or newer available in your shell
- PHP 8.2 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries

View File

@ -20,31 +20,61 @@
<a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest by SemVer)" src="https://img.shields.io/github/downloads/pmmp/PocketMine-MP/latest/total?sort=semver"></a>
</p>
## Getting started
## What is this?
PocketMine-MP is a highly customisable server software for Minecraft: Bedrock Edition, built from scratch in PHP, with over 10 years of history.
If you're looking to create a Minecraft: Bedrock server with **custom functionality**, look no further.
- 🧩 **Powerful plugin API** - extend and customise gameplay as you see fit
- 🗺️ **Rich ecosystem** and **large developer community** - find plugins easily and learn to develop your own
- 🌐 **Multi-world support** - offer a more varied game experience to players without transferring them to other server nodes
- 🏎️ **Performance** - get 100+ players onto one server (depending on hardware and plugins)
- ⤴️ **Continuously updated** - new Minecraft versions are usually supported within days
## :x: PocketMine-MP is NOT a vanilla Minecraft server software.
**It is poorly suited to hosting vanilla survival servers.**
It doesn't have many features from the vanilla game, such as vanilla world generation, redstone, mob AI, and various other things.
If you just want to play **vanilla survival multiplayer**, consider using the [official Minecraft: Bedrock server software](https://minecraft.net/download/server/bedrock) instead of PocketMine-MP.
If that's not an option for you, you may be able to add some of PocketMine-MP's missing features using plugins from [Poggit](https://poggit.pmmp.io/plugins), or write plugins to implement them yourself.
## Getting Started
- [Documentation](http://pmmp.readthedocs.org/)
- [Installation instructions](https://pmmp.readthedocs.io/en/rtfd/installation.html)
- [Docker image](https://github.com/pmmp/PocketMine-MP/pkgs/container/pocketmine-mp)
- [Plugin repository](https://poggit.pmmp.io/plugins)
## Community & Support
- [Forums](https://forums.pmmp.io/)
- [Discord](https://discord.gg/bmSAZBG)
- [StackOverflow](https://stackoverflow.com/tags/pocketmine)
Join our [Discord](https://discord.gg/bmSAZBG) server to chat with other users and developers.
You can also post questions on [StackOverflow](https://stackoverflow.com/tags/pocketmine) under the tag `pocketmine`.
## Developing Plugins
If you want to write your own plugins, the following resources may be useful.
Don't forget you can always ask our community if you need help.
## For developers
* [Building and running from source](BUILDING.md)
* [Developer documentation](https://devdoc.pmmp.io) - General documentation for PocketMine-MP plugin developers
* [Latest release API documentation](https://apidoc.pmmp.io) - Doxygen API documentation generated for each release
* [Latest bleeding-edge API documentation](https://apidoc-dev.pmmp.io) - Doxygen API documentation generated weekly from `major-next` branch
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
## Contributing to PocketMine-MP
PocketMine-MP accepts community contributions! The following resources will be useful if you want to contribute to PocketMine-MP.
* [Building and running PocketMine-MP from source](BUILDING.md)
* [Contributing Guidelines](CONTRIBUTING.md)
## Donate
- Bitcoin Cash (BCH): `qq3r46hn6ljnhnqnfwxt5pg3g447eq9jhvw5ddfear`
PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.
You can support development using the following methods:
- [Patreon](https://www.patreon.com/pocketminemp)
- Bitcoin (BTC): `171u8K9e4FtU6j3e5sqNoxKUgEw9qWQdRV`
- Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T`
- [Patreon](https://www.patreon.com/pocketminemp)
Thanks for your support!
## Licensing information
This project is licensed under LGPL-3.0. Please see the [LICENSE](/LICENSE) file for details.

View File

@ -85,11 +85,10 @@ network:
batch-threshold: 256
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
compression-level: 6
#Use AsyncTasks for compression during the main game session. Increases latency, but may reduce main thread load
async-compression: false
#Threshold for async compression, in bytes. Only packets larger than this will be compressed asynchronously
#Due to large overhead of AsyncTask, async compression isn't worth it except for large packets
async-compression-threshold: 10000
#Max threads to use for packet compression. If disabled, compression will be done on the main thread.
#Set to 0 to disable, or "auto" to try to detect the number of available CPU cores.
#Higher values will allow using more CPU cores, but will also increase memory usage.
compression-threads: auto
#Experimental. Use UPnP to automatically port forward
upnp-forwarding: false
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be

48
src/BootstrapOptions.php Normal file
View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine;
/**
* Constants for all the command-line options that PocketMine-MP supports.
* Other options not listed here can be used to override server.properties and pocketmine.yml values temporarily.
*
* @internal
*/
final class BootstrapOptions{
private function __construct(){
//NOOP
}
/** Disables the setup wizard on first startup */
public const NO_WIZARD = "no-wizard";
/** Force-disables console text colour and formatting */
public const DISABLE_ANSI = "disable-ansi";
/** Force-enables console text colour and formatting */
public const ENABLE_ANSI = "enable-ansi";
/** Path to look in for plugins */
public const PLUGINS = "plugins";
/** Path to store and load server data */
public const DATA = "data";
}

View File

@ -274,8 +274,8 @@ JIT_WARNING
ErrorToExceptionHandler::set();
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
$dataPath = getopt_string("data") ?? $cwd;
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
$dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
$pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
@ -308,10 +308,10 @@ JIT_WARNING
//Logger has a dependency on timezone
Timezone::init();
$opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]);
if(isset($opts["enable-ansi"])){
$opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI]);
if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
Terminal::init(true);
}elseif(isset($opts["disable-ansi"])){
}elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
Terminal::init(false);
}else{
Terminal::init();
@ -324,7 +324,7 @@ JIT_WARNING
$exitCode = 0;
do{
if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts["no-wizard"])){
if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){
$installer = new SetupWizard($dataPath);
if(!$installer->run()){
$exitCode = -1;

View File

@ -50,8 +50,8 @@ use pocketmine\lang\LanguageNotFoundException;
use pocketmine\lang\Translatable;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\CompressBatchTask;
use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\compression\CompressorWorkerPool;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\encryption\EncryptionContext;
@ -208,8 +208,6 @@ class Server{
private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000;
private static ?Server $instance = null;
private TimeTrackingSleeperHandler $tickSleeper;
@ -267,8 +265,13 @@ class Server{
private bool $onlineMode = true;
private Network $network;
private bool $networkCompressionAsync = true;
private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD;
private int $networkCompressionThreads;
/**
* @var CompressorWorkerPool[]
* @phpstan-var array<int, CompressorWorkerPool>
*/
private array $networkCompressionThreadPools = [];
private Language $language;
private bool $forceLanguage = false;
@ -523,7 +526,7 @@ class Server{
return $this->playerDataProvider->loadData($name);
}catch(PlayerDataLoadException $e){
$this->logger->debug("Failed to load player data for $name: " . $e->getMessage());
$this->logger->error($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
$this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
return null;
}
});
@ -542,7 +545,7 @@ class Server{
try{
$this->playerDataProvider->saveData($name, $ev->getSaveData());
}catch(PlayerDataSaveException $e){
$this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
$this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
$this->logger->logException($e);
}
});
@ -854,7 +857,7 @@ class Server{
}
}
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::language_selected($this->getLanguage()->getName(), $this->getLanguage()->getLang())));
$this->logger->info($this->language->translate(KnownTranslationFactory::language_selected($this->language->getName(), $this->language->getLang())));
if(VersionInfo::IS_DEVELOPMENT_BUILD){
if(!$this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_DEV_BUILDS, false)){
@ -877,7 +880,7 @@ class Server{
$this->memoryManager = new MemoryManager($this);
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_start(TextFormat::AQUA . $this->getVersion() . TextFormat::RESET)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_start(TextFormat::AQUA . $this->getVersion() . TextFormat::RESET)));
if(($poolSize = $this->configGroup->getPropertyString(Yml::SETTINGS_ASYNC_WORKERS, "auto")) === "auto"){
$poolSize = 2;
@ -907,11 +910,12 @@ class Server{
}
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
$this->networkCompressionAsync = $this->configGroup->getPropertyBool(Yml::NETWORK_ASYNC_COMPRESSION, true);
$this->networkCompressionAsyncThreshold = max(
$this->configGroup->getPropertyInt(Yml::NETWORK_ASYNC_COMPRESSION_THRESHOLD, self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
$netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
);
$netCompressionThreads = $this->configGroup->getPropertyString(Yml::NETWORK_COMPRESSION_THREADS, "auto");
if($netCompressionThreads === "auto"){
$this->networkCompressionThreads = max(1, Utils::getCoreCount() - 2);
}else{
$this->networkCompressionThreads = max(0, (int) $netCompressionThreads);
}
EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool(Yml::NETWORK_ENABLE_ENCRYPTION, true);
@ -937,11 +941,11 @@ class Server{
$this->onlineMode = $this->configGroup->getConfigBool(ServerProperties::XBOX_AUTH, true);
if($this->onlineMode){
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_auth_enabled()));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_enabled()));
}else{
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_auth_disabled()));
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authWarning()));
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
$this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_disabled()));
$this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authWarning()));
$this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
}
if($this->configGroup->getConfigBool(ServerProperties::HARDCORE, false) && $this->getDifficulty() < World::DIFFICULTY_HARD){
@ -952,17 +956,17 @@ class Server{
$this->serverID = Utils::getMachineUniqueId($this->getIp() . $this->getPort());
$this->getLogger()->debug("Server unique id: " . $this->getServerUniqueId());
$this->getLogger()->debug("Machine unique id: " . Utils::getMachineUniqueId());
$this->logger->debug("Server unique id: " . $this->getServerUniqueId());
$this->logger->debug("Machine unique id: " . Utils::getMachineUniqueId());
$this->network = new Network($this->logger);
$this->network->setName($this->getMotd());
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_info(
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_info(
$this->getName(),
(VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : "") . $this->getPocketMineVersion() . TextFormat::RESET
)));
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
@ -973,7 +977,7 @@ class Server{
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes"));
$this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger);
$this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger);
$pluginGraylist = null;
$graylistFile = Path::join($this->dataPath, "plugin_list.yml");
@ -987,7 +991,7 @@ class Server{
$this->forceShutdownExit();
return;
}
$this->pluginManager = new PluginManager($this, $this->configGroup->getPropertyBool(Yml::PLUGINS_LEGACY_DATA_DIR, true) ? null : Path::join($this->getDataPath(), "plugin_data"), $pluginGraylist);
$this->pluginManager = new PluginManager($this, $this->configGroup->getPropertyBool(Yml::PLUGINS_LEGACY_DATA_DIR, true) ? null : Path::join($this->dataPath, "plugin_data"), $pluginGraylist);
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
$this->pluginManager->registerInterface(new ScriptPluginLoader());
@ -1049,9 +1053,9 @@ class Server{
$this->configGroup->save();
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
$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)))));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
@ -1139,13 +1143,13 @@ class Server{
if($this->worldManager->getDefaultWorld() === null){
$default = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world");
if(trim($default) == ""){
$this->getLogger()->warning("level-name cannot be null, using default");
$this->logger->warning("level-name cannot be null, using default");
$default = "world";
$this->configGroup->setConfigString(ServerProperties::DEFAULT_WORLD_NAME, "world");
}
if(!$this->worldManager->loadWorld($default, true)){
if($this->worldManager->isWorldGenerated($default)){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
return false;
}
@ -1154,7 +1158,7 @@ class Server{
$generatorClass = $getGenerator($generatorName, $generatorOptions, $default);
if($generatorClass === null){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
return false;
}
$creationOptions = WorldCreationOptions::create()
@ -1200,7 +1204,7 @@ class Server{
return false;
}
if($rakLibRegistered){
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
}
if($useQuery){
if(!$rakLibRegistered){
@ -1208,7 +1212,7 @@ class Server{
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($ip, $port, $ipV6, new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
}
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port)));
}
return true;
}
@ -1355,7 +1359,19 @@ class Server{
return count($recipients);
}
private function getNetworkCompressionWorkerPool(Compressor $compressor) : CompressorWorkerPool{
$compressorId = spl_object_id($compressor);
$workerPool = $this->networkCompressionThreadPools[$compressorId] ?? null;
if($workerPool === null){
$this->logger->debug("Creating new worker pool for compressor " . get_class($compressor) . "#" . $compressorId);
$workerPool = $this->networkCompressionThreadPools[$compressorId] = new CompressorWorkerPool($this->networkCompressionThreads, $compressor, $this->tickSleeper);
}
return $workerPool;
}
/**
* @internal
* Broadcasts a list of packets in a batch to a list of players
*
* @param bool|null $sync Compression on the main thread (true) or workers (false). Default is automatic (null).
@ -1367,14 +1383,16 @@ class Server{
if($sync === null){
$threshold = $compressor->getCompressionThreshold();
$sync = !$this->networkCompressionAsync || $threshold === null || strlen($buffer) < $threshold;
$sync = $threshold === null || strlen($buffer) < $threshold;
}
$promise = new CompressBatchPromise();
if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
$task = new CompressBatchTask($buffer, $promise, $compressor);
$this->asyncPool->submitTask($task);
if(!$sync && $this->networkCompressionThreads > 0){
$workerPool = $this->getNetworkCompressionWorkerPool($compressor);
//TODO: we really want to be submitting all sessions' buffers in one go to maximize performance
$promise = $workerPool->submit($buffer);
}else{
$promise = new CompressBatchPromise();
$promise->resolve($compressor->compress($buffer));
}
@ -1455,7 +1473,7 @@ class Server{
$this->shutdown();
if(isset($this->pluginManager)){
$this->getLogger()->debug("Disabling all plugins");
$this->logger->debug("Disabling all plugins");
$this->pluginManager->disablePlugins();
}
@ -1464,37 +1482,41 @@ class Server{
}
if(isset($this->worldManager)){
$this->getLogger()->debug("Unloading all worlds");
$this->logger->debug("Unloading all worlds");
foreach($this->worldManager->getWorlds() as $world){
$this->worldManager->unloadWorld($world, true);
}
}
$this->getLogger()->debug("Removing event handlers");
$this->logger->debug("Removing event handlers");
HandlerListManager::global()->unregisterAll();
if(isset($this->asyncPool)){
$this->getLogger()->debug("Shutting down async task worker pool");
$this->logger->debug("Shutting down async task worker pool");
$this->asyncPool->shutdown();
}
if(isset($this->configGroup)){
$this->getLogger()->debug("Saving properties");
$this->logger->debug("Saving properties");
$this->configGroup->save();
}
if($this->console !== null){
$this->getLogger()->debug("Closing console");
$this->logger->debug("Closing console");
$this->console->quit();
}
if(isset($this->network)){
$this->getLogger()->debug("Stopping network interfaces");
$this->logger->debug("Stopping network interfaces");
foreach($this->network->getInterfaces() as $interface){
$this->getLogger()->debug("Stopping network interface " . get_class($interface));
$this->logger->debug("Stopping network interface " . get_class($interface));
$this->network->unregisterInterface($interface);
}
}
foreach($this->networkCompressionThreadPools as $pool){
$this->logger->debug("Shutting down network compression thread pool for compressor " . get_class($pool->getCompressor()) . "#" . spl_object_id($pool->getCompressor()));
$pool->shutdown();
}
}catch(\Throwable $e){
$this->logger->logException($e);
$this->logger->emergency("Crashed while crashing, killing process");
@ -1559,7 +1581,7 @@ class Server{
}
private function writeCrashDumpFile(CrashDump $dump) : string{
$crashFolder = Path::join($this->getDataPath(), "crashdumps");
$crashFolder = Path::join($this->dataPath, "crashdumps");
if(!is_dir($crashFolder)){
mkdir($crashFolder);
}
@ -1590,17 +1612,17 @@ class Server{
ini_set("error_reporting", '0');
ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems
try{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this, $this->pluginManager ?? null);
$crashDumpPath = $this->writeCrashDumpFile($dump);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
if($this->configGroup->getPropertyBool(Yml::AUTO_REPORT_ENABLED, true)){
$report = true;
$stamp = Path::join($this->getDataPath(), "crashdumps", ".last_crash");
$stamp = Path::join($this->dataPath, "crashdumps", ".last_crash");
$crashInterval = 120; //2 minutes
if(($lastReportTime = @filemtime($stamp)) !== false && $lastReportTime + $crashInterval >= time()){
$report = false;
@ -1631,7 +1653,7 @@ class Server{
if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){
$reportId = $data->crashId;
$reportUrl = $data->crashUrl;
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
}elseif(isset($data->error) && is_string($data->error)){
$this->logger->emergency("Automatic crash report submission failed: $data->error");
}else{
@ -1645,7 +1667,7 @@ class Server{
}catch(\Throwable $e){
$this->logger->logException($e);
try{
$this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_error($e->getMessage())));
$this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_crash_error($e->getMessage())));
}catch(\Throwable $e){}
}
@ -1763,7 +1785,7 @@ class Server{
echo "\x1b]0;" . $this->getName() . " " .
$this->getPocketMineVersion() .
" | Online $online/" . $this->getMaxPlayers() .
" | Online $online/" . $this->maxPlayers .
($connecting > 0 ? " (+$connecting connecting)" : "") .
" | Memory " . $usage .
" | U " . round($bandwidthStats->getSend()->getAverageBytes() / 1024, 2) .
@ -1828,10 +1850,10 @@ class Server{
}
if(($this->tickCounter % self::TICKS_PER_TPS_OVERLOAD_WARNING) === 0 && $this->getTicksPerSecondAverage() < self::TPS_OVERLOAD_WARNING_THRESHOLD){
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
$this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
}
$this->getMemoryManager()->check();
$this->memoryManager->check();
if($this->console !== null){
Timings::$serverCommand->startTiming();

View File

@ -24,7 +24,9 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\snooze\SleeperHandler;
use pocketmine\snooze\SleeperHandlerEntry;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\Utils;
use function hrtime;
/**
@ -35,12 +37,29 @@ final class TimeTrackingSleeperHandler extends SleeperHandler{
private int $notificationProcessingTimeNs = 0;
/**
* @var TimingsHandler[]
* @phpstan-var array<string, TimingsHandler>
*/
private static array $handlerTimings = [];
public function __construct(
private TimingsHandler $timings
){
parent::__construct();
}
public function addNotifier(\Closure $handler) : SleeperHandlerEntry{
$name = Utils::getNiceClosureName($handler);
$timings = self::$handlerTimings[$name] ??= new TimingsHandler("Snooze Handler: " . $name, $this->timings);
return parent::addNotifier(function() use ($timings, $handler) : void{
$timings->startTiming();
$handler();
$timings->stopTiming();
});
}
/**
* Returns the time in nanoseconds spent processing notifications since the last reset.
*/

View File

@ -31,8 +31,8 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.8.2";
public const IS_DEVELOPMENT_BUILD = false;
public const BASE_VERSION = "5.8.3";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";
/**

View File

@ -90,10 +90,9 @@ final class YmlServerProperties{
public const MEMORY_WORLD_CACHES_DISABLE_CHUNK_CACHE = 'memory.world-caches.disable-chunk-cache';
public const MEMORY_WORLD_CACHES_LOW_MEMORY_TRIGGER = 'memory.world-caches.low-memory-trigger';
public const NETWORK = 'network';
public const NETWORK_ASYNC_COMPRESSION = 'network.async-compression';
public const NETWORK_ASYNC_COMPRESSION_THRESHOLD = 'network.async-compression-threshold';
public const NETWORK_BATCH_THRESHOLD = 'network.batch-threshold';
public const NETWORK_COMPRESSION_LEVEL = 'network.compression-level';
public const NETWORK_COMPRESSION_THREADS = 'network.compression-threads';
public const NETWORK_ENABLE_ENCRYPTION = 'network.enable-encryption';
public const NETWORK_MAX_MTU_SIZE = 'network.max-mtu-size';
public const NETWORK_UPNP_FORWARDING = 'network.upnp-forwarding';

View File

@ -173,7 +173,7 @@ class Bamboo extends Transparent{
$newHeight = $height + $growAmount;
$stemBlock = (clone $this)->setReady(false)->setLeafSize(self::NO_LEAVES);
if($newHeight >= 4 && !$stemBlock->isThick()){ //don't change it to false if height is less, because it might have been chopped
if($newHeight >= 4 && !$stemBlock->thick){ //don't change it to false if height is less, because it might have been chopped
$stemBlock = $stemBlock->setThick(true);
}
$smallLeavesBlock = (clone $stemBlock)->setLeafSize(self::SMALL_LEAVES);

View File

@ -55,12 +55,12 @@ class Barrel extends Opaque{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
if(abs($player->getPosition()->getX() - $this->position->getX()) < 2 && abs($player->getPosition()->getZ() - $this->position->getZ()) < 2){
$y = $player->getEyePos()->getY();
if(abs($player->getPosition()->x - $this->position->x) < 2 && abs($player->getPosition()->z - $this->position->z) < 2){
$y = $player->getEyePos()->y;
if($y - $this->position->getY() > 2){
if($y - $this->position->y > 2){
$this->facing = Facing::UP;
}elseif($this->position->getY() - $y > 0){
}elseif($this->position->y - $y > 0){
$this->facing = Facing::DOWN;
}else{
$this->facing = Facing::opposite($player->getHorizontalFacing());

View File

@ -65,8 +65,8 @@ abstract class BaseBigDripleaf extends Transparent{
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
if($block instanceof BaseBigDripleaf){
$this->facing = $block->getFacing();
$tx->addBlock($block->getPosition(), VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($this->facing));
$this->facing = $block->facing;
$tx->addBlock($block->position, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($this->facing));
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@ -98,7 +98,7 @@ abstract class BaseBigDripleaf extends Transparent{
if($head === null){
return false;
}
$pos = $head->getPosition();
$pos = $head->position;
$up = $pos->up();
$world = $pos->getWorld();
if(
@ -110,8 +110,8 @@ abstract class BaseBigDripleaf extends Transparent{
$tx = new BlockTransaction($world);
$tx->addBlock($pos, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($head->getFacing()));
$tx->addBlock($up, VanillaBlocks::BIG_DRIPLEAF_HEAD()->setFacing($head->getFacing()));
$tx->addBlock($pos, VanillaBlocks::BIG_DRIPLEAF_STEM()->setFacing($head->facing));
$tx->addBlock($up, VanillaBlocks::BIG_DRIPLEAF_HEAD()->setFacing($head->facing));
$ev = new StructureGrowEvent($head, $tx, $player);
$ev->call();

View File

@ -145,7 +145,7 @@ class Bed extends Transparent{
$b = ($this->isHeadPart() ? $this : $other);
if($b->isOccupied()){
if($b->occupied){
$player->sendMessage(KnownTranslationFactory::tile_bed_occupied()->prefix(TextFormat::GRAY));
return true;

View File

@ -270,11 +270,22 @@ class Block{
}
private function encodeFullState() : int{
$writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits);
$writer->writeInt($this->requiredBlockItemStateDataBits, $this->encodeBlockItemState());
$writer->writeInt($this->requiredBlockOnlyStateDataBits, $this->encodeBlockOnlyState());
$blockItemBits = $this->requiredBlockItemStateDataBits;
$blockOnlyBits = $this->requiredBlockOnlyStateDataBits;
return $writer->getValue();
if($blockOnlyBits === 0 && $blockItemBits === 0){
return 0;
}
$result = 0;
if($blockItemBits > 0){
$result |= $this->encodeBlockItemState();
}
if($blockOnlyBits > 0){
$result |= $this->encodeBlockOnlyState() << $blockItemBits;
}
return $result;
}
/**

View File

@ -97,7 +97,7 @@ class ChiseledBookshelf extends Opaque{
return false;
}
$x = Facing::axis($face) === Axis::X ? $clickVector->getZ() : $clickVector->getX();
$x = Facing::axis($face) === Axis::X ? $clickVector->z : $clickVector->x;
$slot = ChiseledBookshelfSlot::fromBlockFaceCoordinates(
Facing::isPositive(Facing::rotateY($face, true)) ? 1 - $x : $x,
$clickVector->y

View File

@ -54,7 +54,7 @@ final class ChorusFlower extends Flowable{
}
private function canBeSupportedAt(Block $block) : bool{
$position = $block->getPosition();
$position = $block->position;
$world = $position->getWorld();
$down = $world->getBlock($position->down());
@ -152,7 +152,7 @@ final class ChorusFlower extends Flowable{
if($tx === null){
$tx = new BlockTransaction($this->position->getWorld());
}
$tx->addBlock($this->position->getSide($facing), (clone $this)->setAge(min(self::MAX_AGE, $this->getAge() + $ageChange)));
$tx->addBlock($this->position->getSide($facing), (clone $this)->setAge(min(self::MAX_AGE, $this->age + $ageChange)));
return $tx;
}

View File

@ -51,7 +51,7 @@ final class ChorusPlant extends Flowable{
}
private function canBeSupportedAt(Block $block) : bool{
$position = $block->getPosition();
$position = $block->position;
$world = $position->getWorld();
$down = $world->getBlock($position->down());

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CropGrowthHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
@ -66,7 +67,7 @@ abstract class Crops extends Flowable{
}
public function onRandomTick() : void{
if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){
if($this->age < self::MAX_AGE && CropGrowthHelper::canGrow($this)){
$block = clone $this;
++$block->age;
BlockEventHelper::grow($this, $block, null);

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\CropGrowthHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
@ -34,7 +35,6 @@ use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
final class DoublePitcherCrop extends DoublePlant{
use AgeableTrait {
@ -101,9 +101,8 @@ final class DoublePitcherCrop extends DoublePlant{
}
public function onRandomTick() : void{
//TODO: the growth speed is influenced by farmland and nearby crops
//only the bottom half of the plant can grow randomly
if(mt_rand(0, 2) === 0 && !$this->top){
if(CropGrowthHelper::canGrow($this) && !$this->top){
$this->grow(null);
}
}

View File

@ -152,7 +152,7 @@ class Farmland extends Transparent{
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){
$this->getPosition()->getWorld()->setBlock($this->getPosition(), VanillaBlocks::DIRT());
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
}
}
return null;

View File

@ -140,7 +140,7 @@ class Fire extends BaseFire{
$block->onIncinerate();
$world = $this->position->getWorld();
if($world->getBlock($block->getPosition())->isSameState($block)){
if($world->getBlock($block->position)->isSameState($block)){
$spreadedFire = false;
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
$fire = clone $this;

View File

@ -58,7 +58,7 @@ final class FloorCoralFan extends BaseCoral{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$playerBlockPos = $player->getPosition()->floor();
$directionVector = $blockReplace->getPosition()->subtractVector($playerBlockPos)->normalize();
$directionVector = $blockReplace->position->subtractVector($playerBlockPos)->normalize();
$angle = rad2deg(atan2($directionVector->getZ(), $directionVector->getX()));
if($angle <= 45 || 315 <= $angle || (135 <= $angle && $angle <= 225)){

View File

@ -61,7 +61,7 @@ class Jukebox extends Opaque{
public function ejectRecord() : void{
if($this->record !== null){
$this->getPosition()->getWorld()->dropItem($this->getPosition()->add(0.5, 1, 0.5), $this->record);
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $this->record);
$this->record = null;
$this->stopSound();
}
@ -76,12 +76,12 @@ class Jukebox extends Opaque{
public function startSound() : void{
if($this->record !== null){
$this->getPosition()->getWorld()->addSound($this->getPosition(), new RecordSound($this->record->getRecordType()));
$this->position->getWorld()->addSound($this->position, new RecordSound($this->record->getRecordType()));
}
}
public function stopSound() : void{
$this->getPosition()->getWorld()->addSound($this->getPosition(), new RecordStopSound());
$this->position->getWorld()->addSound($this->position, new RecordStopSound());
}
public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -108,8 +108,8 @@ class NetherVines extends Flowable{
private function grow(?Player $player, int $growthAmount = 1) : bool{
$top = $this->seekToTip();
$age = $top->getAge();
$pos = $top->getPosition();
$age = $top->age;
$pos = $top->position;
$world = $pos->getWorld();
$changedBlocks = 0;

View File

@ -70,13 +70,13 @@ class PinkPetals extends Flowable{
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return ($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT) || $this->supportedWhenPlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
return ($blockReplace instanceof PinkPetals && $blockReplace->count < self::MAX_COUNT) || $this->supportedWhenPlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT){
$this->count = $blockReplace->getCount() + 1;
$this->facing = $blockReplace->getFacing();
if($blockReplace instanceof PinkPetals && $blockReplace->count < self::MAX_COUNT){
$this->count = $blockReplace->count + 1;
$this->facing = $blockReplace->facing;
}elseif($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CropGrowthHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
@ -35,7 +36,6 @@ use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
final class PitcherCrop extends Flowable{
use AgeableTrait;
@ -97,8 +97,7 @@ final class PitcherCrop extends Flowable{
}
public function onRandomTick() : void{
//TODO: the growth speed is influenced by farmland and nearby crops
if(mt_rand(0, 2) === 0){
if(CropGrowthHelper::canGrow($this)){
$this->grow(null);
}
}

View File

@ -43,7 +43,7 @@ class RedMushroom extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
$position = $this->getPosition();
$position = $this->position;
$lightLevel = $position->getWorld()->getFullLightAt($position->x, $position->y, $position->z);
$downId = $down->getTypeId();
//TODO: nylium support

View File

@ -83,7 +83,7 @@ class SmallDripleaf extends Transparent{
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
$tx->addBlock($block->getPosition(), VanillaBlocks::SMALL_DRIPLEAF()
$tx->addBlock($block->position, VanillaBlocks::SMALL_DRIPLEAF()
->setFacing($this->facing)
->setTop(true)
);
@ -117,7 +117,7 @@ class SmallDripleaf extends Transparent{
$height = mt_rand(2, 5);
$grown = 0;
for($i = 0; $i < $height; $i++){
$pos = $bottomBlock->getSide(Facing::UP, $i)->getPosition();
$pos = $bottomBlock->getSide(Facing::UP, $i)->position;
if(!$this->canGrowTo($pos)){
break;
}

View File

@ -106,8 +106,8 @@ class Stair extends Transparent{
public function getSupportType(int $facing) : SupportType{
if(
$facing === Facing::UP && $this->isUpsideDown() ||
$facing === Facing::DOWN && !$this->isUpsideDown() ||
$facing === Facing::UP && $this->upsideDown ||
$facing === Facing::DOWN && !$this->upsideDown ||
($facing === $this->facing && $this->shape !== StairShape::OUTER_LEFT && $this->shape !== StairShape::OUTER_RIGHT) ||
($facing === Facing::rotate($this->facing, Axis::Y, false) && $this->shape === StairShape::INNER_LEFT) ||
($facing === Facing::rotate($this->facing, Axis::Y, true) && $this->shape === StairShape::INNER_RIGHT)

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CropGrowthHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\math\Facing;
@ -63,7 +64,7 @@ abstract class Stem extends Crops{
}
public function onRandomTick() : void{
if($this->facing === Facing::UP && mt_rand(0, 2) === 1){
if($this->facing === Facing::UP && CropGrowthHelper::canGrow($this)){
$world = $this->position->getWorld();
if($this->age < self::MAX_AGE){
$block = clone $this;

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CropGrowthHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
@ -32,7 +33,6 @@ use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function mt_rand;
final class TorchflowerCrop extends Flowable{
use StaticSupportTrait;
@ -79,7 +79,7 @@ final class TorchflowerCrop extends Flowable{
}
public function onRandomTick() : void{
if(mt_rand(0, 2) === 1){
if(CropGrowthHelper::canGrow($this)){
BlockEventHelper::grow($this, $this->getNextState(), null);
}
}

View File

@ -47,20 +47,20 @@ trait AnimatedBlockInventoryTrait{
public function onOpen(Player $who) : void{
parent::onOpen($who);
if($this->getHolder()->isValid() && $this->getViewerCount() === 1){
if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(true);
$this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getOpenSound());
$this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getOpenSound());
}
}
abstract protected function animateBlock(bool $isOpen) : void;
public function onClose(Player $who) : void{
if($this->getHolder()->isValid() && $this->getViewerCount() === 1){
if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(false);
$this->getHolder()->getWorld()->addSound($this->getHolder()->add(0.5, 0.5, 0.5), $this->getCloseSound());
$this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getCloseSound());
}
parent::onClose($who);
}

View File

@ -139,7 +139,7 @@ class Chest extends Spawnable implements Container, Nameable{
if($pair->doubleInventory !== null){
$this->doubleInventory = $pair->doubleInventory;
}else{
if(($pair->getPosition()->x + ($pair->getPosition()->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly
if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory);
}else{
$this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory);

View File

@ -0,0 +1,120 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\Block;
use pocketmine\block\Farmland;
use function mt_rand;
final class CropGrowthHelper{
private const ON_HYDRATED_FARMLAND_BONUS = 3;
private const ON_DRY_FARMLAND_BONUS = 1;
private const ADJACENT_HYDRATED_FARMLAND_BONUS = 3 / 4;
private const ADJACENT_DRY_FARMLAND_BONUS = 1 / 4;
private const IMPROPER_ARRANGEMENT_DIVISOR = 2;
private const MIN_LIGHT_LEVEL = 9;
private function __construct(){
//NOOP
}
/**
* Returns the speed at which this crop will grow, depending on its surroundings.
* The default is once every 26 random ticks.
*
* Things which influence this include nearby farmland (bonus for hydrated farmland) and the position of other
* nearby crops of the same type (nearby crops of the same type will negatively influence growth speed unless
* planted in rows and properly spaced apart).
*/
public static function calculateMultiplier(Block $block) : float{
$result = 1;
$position = $block->getPosition();
$world = $position->getWorld();
$baseX = $position->getFloorX();
$baseY = $position->getFloorY();
$baseZ = $position->getFloorZ();
$farmland = $world->getBlockAt($baseX, $baseY - 1, $baseZ);
if($farmland instanceof Farmland){
$result += $farmland->getWetness() > 0 ? self::ON_HYDRATED_FARMLAND_BONUS : self::ON_DRY_FARMLAND_BONUS;
}
$xRow = false;
$zRow = false;
$improperArrangement = false;
for($x = -1; $x <= 1; $x++){
for($z = -1; $z <= 1; $z++){
if($x === 0 && $z === 0){
continue;
}
$nextFarmland = $world->getBlockAt($baseX + $x, $baseY - 1, $baseZ + $z);
if(!$nextFarmland instanceof Farmland){
continue;
}
$result += $nextFarmland->getWetness() > 0 ? self::ADJACENT_HYDRATED_FARMLAND_BONUS : self::ADJACENT_DRY_FARMLAND_BONUS;
if(!$improperArrangement){
$nextCrop = $world->getBlockAt($baseX + $x, $baseY, $baseZ + $z);
if($nextCrop->hasSameTypeId($block)){
match(0){
$x => $zRow ? $improperArrangement = true : $xRow = true,
$z => $xRow ? $improperArrangement = true : $zRow = true,
default => $improperArrangement = true,
};
}
}
}
}
//crops can be arranged in rows, but the rows must not cross and must be spaced apart by at least one block
if($improperArrangement){
$result /= self::IMPROPER_ARRANGEMENT_DIVISOR;
}
return $result;
}
public static function hasEnoughLight(Block $block, int $minLevel = self::MIN_LIGHT_LEVEL) : bool{
$position = $block->getPosition();
$world = $position->getWorld();
//crop growth is not affected by time of day since 1.11 or so
return $world->getPotentialLightAt($position->x, $position->y, $position->z) >= $minLevel;
}
public static function canGrow(Block $block) : bool{
//while it may be tempting to use mt_rand(0, 25) < multiplier, this would make crops grow a bit faster than
//vanilla in most cases due to the remainder of 25 / multiplier not being discarded
return mt_rand(0, (int) (25 / self::calculateMultiplier($block))) === 0 && self::hasEnoughLight($block);
}
}

View File

@ -193,6 +193,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::CLAY_BALL, Items::CLAY());
$this->map1to1Item(Ids::CLOCK, Items::CLOCK());
$this->map1to1Item(Ids::COAL, Items::COAL());
$this->map1to1Item(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::COCOA_BEANS, Items::COCOA_BEANS());
$this->map1to1Item(Ids::COD, Items::RAW_FISH());
$this->map1to1Item(Ids::COMPASS, Items::COMPASS());
@ -221,6 +222,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::DISC_FRAGMENT_5, Items::DISC_FRAGMENT_5());
$this->map1to1Item(Ids::DRAGON_BREATH, Items::DRAGON_BREATH());
$this->map1to1Item(Ids::DRIED_KELP, Items::DRIED_KELP());
$this->map1to1Item(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ECHO_SHARD, Items::ECHO_SHARD());
$this->map1to1Item(Ids::EGG, Items::EGG());
$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
@ -228,6 +230,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());
$this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::FEATHER, Items::FEATHER());
$this->map1to1Item(Ids::FERMENTED_SPIDER_EYE, Items::FERMENTED_SPIDER_EYE());
$this->map1to1Item(Ids::FIRE_CHARGE, Items::FIRE_CHARGE());
@ -257,6 +260,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::HEART_OF_THE_SEA, Items::HEART_OF_THE_SEA());
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
@ -316,6 +320,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::NETHERITE_SCRAP, Items::NETHERITE_SCRAP());
$this->map1to1Item(Ids::NETHERITE_SHOVEL, Items::NETHERITE_SHOVEL());
$this->map1to1Item(Ids::NETHERITE_SWORD, Items::NETHERITE_SWORD());
$this->map1to1Item(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::OAK_BOAT, Items::OAK_BOAT());
$this->map1to1Item(Ids::OAK_SIGN, Items::OAK_SIGN());
$this->map1to1Item(Ids::PAINTING, Items::PAINTING());
@ -335,18 +340,25 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::RABBIT_FOOT, Items::RABBIT_FOOT());
$this->map1to1Item(Ids::RABBIT_HIDE, Items::RABBIT_HIDE());
$this->map1to1Item(Ids::RABBIT_STEW, Items::RABBIT_STEW());
$this->map1to1Item(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON());
$this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST());
$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
$this->map1to1Item(Ids::SALMON, Items::RAW_SALMON());
$this->map1to1Item(Ids::SCUTE, Items::SCUTE());
$this->map1to1Item(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SHEARS, Items::SHEARS());
$this->map1to1Item(Ids::SHULKER_SHELL, Items::SHULKER_SHELL());
$this->map1to1Item(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SLIME_BALL, Items::SLIMEBALL());
$this->map1to1Item(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SNOWBALL, Items::SNOWBALL());
$this->map1to1Item(Ids::SPIDER_EYE, Items::SPIDER_EYE());
$this->map1to1Item(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::SPRUCE_BOAT, Items::SPRUCE_BOAT());
$this->map1to1Item(Ids::SPRUCE_SIGN, Items::SPRUCE_SIGN());
$this->map1to1Item(Ids::SPYGLASS, Items::SPYGLASS());
@ -361,14 +373,19 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::SUGAR, Items::SUGAR());
$this->map1to1Item(Ids::SWEET_BERRIES, Items::SWEET_BERRIES());
$this->map1to1Item(Ids::TORCHFLOWER_SEEDS, Items::TORCHFLOWER_SEEDS());
$this->map1to1Item(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::TOTEM_OF_UNDYING, Items::TOTEM());
$this->map1to1Item(Ids::TROPICAL_FISH, Items::CLOWNFISH());
$this->map1to1Item(Ids::TURTLE_HELMET, Items::TURTLE_HELMET());
$this->map1to1Item(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE, Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::VILLAGER_SPAWN_EGG, Items::VILLAGER_SPAWN_EGG());
$this->map1to1Item(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WARPED_SIGN, Items::WARPED_SIGN());
$this->map1to1Item(Ids::WATER_BUCKET, Items::WATER_BUCKET());
$this->map1to1Item(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WHEAT, Items::WHEAT());
$this->map1to1Item(Ids::WHEAT_SEEDS, Items::WHEAT_SEEDS());
$this->map1to1Item(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE, Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::WOODEN_AXE, Items::WOODEN_AXE());
$this->map1to1Item(Ids::WOODEN_HOE, Items::WOODEN_HOE());
$this->map1to1Item(Ids::WOODEN_PICKAXE, Items::WOODEN_PICKAXE());

View File

@ -305,8 +305,25 @@ final class ItemTypeIds{
public const CHERRY_SIGN = 20266;
public const ENCHANTED_BOOK = 20267;
public const TORCHFLOWER_SEEDS = 20268;
public const NETHERITE_UPGRADE_SMITHING_TEMPLATE = 20269;
public const SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE = 20270;
public const VEX_ARMOR_TRIM_SMITHING_TEMPLATE = 20271;
public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = 20272;
public const COAST_ARMOR_TRIM_SMITHING_TEMPLATE = 20273;
public const DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = 20274;
public const WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = 20275;
public const RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = 20276;
public const SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = 20277;
public const HOST_ARMOR_TRIM_SMITHING_TEMPLATE = 20278;
public const WARD_ARMOR_TRIM_SMITHING_TEMPLATE = 20279;
public const SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = 20280;
public const TIDE_ARMOR_TRIM_SMITHING_TEMPLATE = 20281;
public const SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = 20282;
public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = 20283;
public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = 20284;
public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285;
public const FIRST_UNUSED_ITEM_ID = 20269;
public const FIRST_UNUSED_ITEM_ID = 20286;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -868,6 +868,8 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("pink_petals", fn() => Blocks::PINK_PETALS());
$result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP());
$result->registerBlock("piglin_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PIGLIN));
$result->registerBlock("pitcher_plant", fn() => Blocks::PITCHER_PLANT());
$result->registerBlock("pitcher_pod", fn() => Blocks::PITCHER_CROP());
$result->registerBlock("plank", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("planks", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("player_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PLAYER));
@ -1264,6 +1266,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("clown_fish", fn() => Items::CLOWNFISH());
$result->register("clownfish", fn() => Items::CLOWNFISH());
$result->register("coal", fn() => Items::COAL());
$result->register("coast_armor_trim_smithing_template", fn() => Items::COAST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("cocoa_beans", fn() => Items::COCOA_BEANS());
$result->register("cod", fn() => Items::RAW_FISH());
$result->register("compass", fn() => Items::COMPASS());
@ -1292,6 +1295,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("disc_fragment_5", fn() => Items::DISC_FRAGMENT_5());
$result->register("dragon_breath", fn() => Items::DRAGON_BREATH());
$result->register("dried_kelp", fn() => Items::DRIED_KELP());
$result->register("dune_armor_trim_smithing_template", fn() => Items::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("dye", fn() => Items::INK_SAC());
$result->register("echo_shard", fn() => Items::ECHO_SHARD());
$result->register("egg", fn() => Items::EGG());
@ -1302,6 +1306,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("enchanting_bottle", fn() => Items::EXPERIENCE_BOTTLE());
$result->register("ender_pearl", fn() => Items::ENDER_PEARL());
$result->register("experience_bottle", fn() => Items::EXPERIENCE_BOTTLE());
$result->register("eye_armor_trim_smithing_template", fn() => Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("eye_drops", fn() => Items::MEDICINE()->setType(MedicineType::EYE_DROPS));
$result->register("feather", fn() => Items::FEATHER());
$result->register("fermented_spider_eye", fn() => Items::FERMENTED_SPIDER_EYE());
@ -1343,6 +1348,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("gunpowder", fn() => Items::GUNPOWDER());
$result->register("heart_of_the_sea", fn() => Items::HEART_OF_THE_SEA());
$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
$result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("honeycomb", fn() => Items::HONEYCOMB());
$result->register("ink_sac", fn() => Items::INK_SAC());
$result->register("iron_axe", fn() => Items::IRON_AXE());
@ -1396,6 +1402,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("netherite_shovel", fn() => Items::NETHERITE_SHOVEL());
$result->register("netherite_sword", fn() => Items::NETHERITE_SWORD());
$result->register("netherstar", fn() => Items::NETHER_STAR());
$result->register("netherite_upgrade_smithing_template", fn() => Items::NETHERITE_UPGRADE_SMITHING_TEMPLATE());
$result->register("oak_boat", fn() => Items::OAK_BOAT());
$result->register("painting", fn() => Items::PAINTING());
$result->register("paper", fn() => Items::PAPER());
@ -1416,6 +1423,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("rabbit_foot", fn() => Items::RABBIT_FOOT());
$result->register("rabbit_hide", fn() => Items::RABBIT_HIDE());
$result->register("rabbit_stew", fn() => Items::RABBIT_STEW());
$result->register("raiser_armor_trim_smithing_template", fn() => Items::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("raw_beef", fn() => Items::RAW_BEEF());
$result->register("raw_cod", fn() => Items::RAW_FISH());
$result->register("raw_copper", fn() => Items::RAW_COPPER());
@ -1444,17 +1452,23 @@ final class StringToItemParser extends StringToTParser{
$result->register("record_ward", fn() => Items::RECORD_WARD());
$result->register("redstone", fn() => Items::REDSTONE_DUST());
$result->register("redstone_dust", fn() => Items::REDSTONE_DUST());
$result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("rotten_flesh", fn() => Items::ROTTEN_FLESH());
$result->register("salmon", fn() => Items::RAW_SALMON());
$result->register("scute", fn() => Items::SCUTE());
$result->register("sentry_armor_trim_smithing_template", fn() => Items::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("shaper_armor_trim_smithing_template", fn() => Items::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("seeds", fn() => Items::WHEAT_SEEDS());
$result->register("shears", fn() => Items::SHEARS());
$result->register("shulker_shell", fn() => Items::SHULKER_SHELL());
$result->register("silence_armor_trim_smithing_template", fn() => Items::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("slime_ball", fn() => Items::SLIMEBALL());
$result->register("snout_armor_trim_smithing_template", fn() => Items::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("slimeball", fn() => Items::SLIMEBALL());
$result->register("snowball", fn() => Items::SNOWBALL());
$result->register("speckled_melon", fn() => Items::GLISTERING_MELON());
$result->register("spider_eye", fn() => Items::SPIDER_EYE());
$result->register("spire_armor_trim_smithing_template", fn() => Items::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("splash_potion", fn() => Items::SPLASH_POTION());
$result->register("spruce_boat", fn() => Items::SPRUCE_BOAT());
$result->register("spyglass", fn() => Items::SPYGLASS());
@ -1473,13 +1487,18 @@ final class StringToItemParser extends StringToTParser{
$result->register("sweet_berries", fn() => Items::SWEET_BERRIES());
$result->register("tonic", fn() => Items::MEDICINE()->setType(MedicineType::TONIC));
$result->register("torchflower_seeds", fn() => Items::TORCHFLOWER_SEEDS());
$result->register("tide_armor_trim_smithing_template", fn() => Items::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("totem", fn() => Items::TOTEM());
$result->register("turtle_helmet", fn() => Items::TURTLE_HELMET());
$result->register("vex_armor_trim_smithing_template", fn() => Items::VEX_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("turtle_shell_piece", fn() => Items::SCUTE());
$result->register("villager_spawn_egg", fn() => Items::VILLAGER_SPAWN_EGG());
$result->register("ward_armor_trim_smithing_template", fn() => Items::WARD_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("water_bucket", fn() => Items::WATER_BUCKET());
$result->register("wayfinder_armor_trim_smithing_template", fn() => Items::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("wheat", fn() => Items::WHEAT());
$result->register("wheat_seeds", fn() => Items::WHEAT_SEEDS());
$result->register("wild_armor_trim_smithing_template", fn() => Items::WILD_ARMOR_TRIM_SMITHING_TEMPLATE());
$result->register("wooden_axe", fn() => Items::WOODEN_AXE());
$result->register("wooden_hoe", fn() => Items::WOODEN_HOE());
$result->register("wooden_pickaxe", fn() => Items::WOODEN_PICKAXE());

View File

@ -121,6 +121,7 @@ use function strtolower;
* @method static Clock CLOCK()
* @method static Clownfish CLOWNFISH()
* @method static Coal COAL()
* @method static Item COAST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static CocoaBeans COCOA_BEANS()
* @method static Compass COMPASS()
* @method static CookedChicken COOKED_CHICKEN()
@ -148,6 +149,7 @@ use function strtolower;
* @method static Item DISC_FRAGMENT_5()
* @method static Item DRAGON_BREATH()
* @method static DriedKelp DRIED_KELP()
* @method static Item DUNE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Dye DYE()
* @method static Item ECHO_SHARD()
* @method static Egg EGG()
@ -156,6 +158,7 @@ use function strtolower;
* @method static GoldenAppleEnchanted ENCHANTED_GOLDEN_APPLE()
* @method static EnderPearl ENDER_PEARL()
* @method static ExperienceBottle EXPERIENCE_BOTTLE()
* @method static Item EYE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item FEATHER()
* @method static Item FERMENTED_SPIDER_EYE()
* @method static FireCharge FIRE_CHARGE()
@ -185,6 +188,7 @@ use function strtolower;
* @method static Item HEART_OF_THE_SEA()
* @method static Item HONEYCOMB()
* @method static HoneyBottle HONEY_BOTTLE()
* @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item INK_SAC()
* @method static Axe IRON_AXE()
* @method static Armor IRON_BOOTS()
@ -227,6 +231,7 @@ use function strtolower;
* @method static Item NETHERITE_SCRAP()
* @method static Shovel NETHERITE_SHOVEL()
* @method static Sword NETHERITE_SWORD()
* @method static Item NETHERITE_UPGRADE_SMITHING_TEMPLATE()
* @method static Item NETHER_BRICK()
* @method static Item NETHER_QUARTZ()
* @method static Item NETHER_STAR()
@ -247,6 +252,7 @@ use function strtolower;
* @method static Item RABBIT_FOOT()
* @method static Item RABBIT_HIDE()
* @method static RabbitStew RABBIT_STEW()
* @method static Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RawBeef RAW_BEEF()
* @method static RawChicken RAW_CHICKEN()
* @method static Item RAW_COPPER()
@ -273,13 +279,19 @@ use function strtolower;
* @method static Record RECORD_WAIT()
* @method static Record RECORD_WARD()
* @method static Redstone REDSTONE_DUST()
* @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RottenFlesh ROTTEN_FLESH()
* @method static Item SCUTE()
* @method static Item SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Shears SHEARS()
* @method static Item SHULKER_SHELL()
* @method static Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item SLIMEBALL()
* @method static Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Snowball SNOWBALL()
* @method static SpiderEye SPIDER_EYE()
* @method static Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static SplashPotion SPLASH_POTION()
* @method static Boat SPRUCE_BOAT()
* @method static ItemBlockWallOrFloor SPRUCE_SIGN()
@ -296,14 +308,19 @@ use function strtolower;
* @method static Item SUGAR()
* @method static SuspiciousStew SUSPICIOUS_STEW()
* @method static SweetBerries SWEET_BERRIES()
* @method static Item TIDE_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static TorchflowerSeeds TORCHFLOWER_SEEDS()
* @method static Totem TOTEM()
* @method static TurtleHelmet TURTLE_HELMET()
* @method static Item VEX_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static SpawnEgg VILLAGER_SPAWN_EGG()
* @method static Item WARD_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static ItemBlockWallOrFloor WARPED_SIGN()
* @method static LiquidBucket WATER_BUCKET()
* @method static Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Item WHEAT()
* @method static WheatSeeds WHEAT_SEEDS()
* @method static Item WILD_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static Axe WOODEN_AXE()
* @method static Hoe WOODEN_HOE()
* @method static Pickaxe WOODEN_PICKAXE()
@ -339,6 +356,7 @@ final class VanillaItems{
self::registerArmorItems();
self::registerSpawnEggs();
self::registerTierToolItems();
self::registerSmithingTemplates();
self::register("air", Blocks::AIR()->asItem()->setCount(0));
@ -644,4 +662,24 @@ final class VanillaItems{
self::register("netherite_leggings", new Armor(new IID(Ids::NETHERITE_LEGGINGS), "Netherite Leggings", new ArmorTypeInfo(6, 556, ArmorInventory::SLOT_LEGS, 3, true, material: ArmorMaterials::NETHERITE()), [EnchantmentTags::LEGGINGS]));
}
private static function registerSmithingTemplates() : void{
self::register("netherite_upgrade_smithing_template", new Item(new IID(Ids::NETHERITE_UPGRADE_SMITHING_TEMPLATE), "Netherite Upgrade Smithing Template"));
self::register("coast_armor_trim_smithing_template", new Item(new IID(Ids::COAST_ARMOR_TRIM_SMITHING_TEMPLATE), "Coast Armor Trim Smithing Template"));
self::register("dune_armor_trim_smithing_template", new Item(new IID(Ids::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), "Dune Armor Trim Smithing Template"));
self::register("eye_armor_trim_smithing_template", new Item(new IID(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE), "Eye Armor Trim Smithing Template"));
self::register("host_armor_trim_smithing_template", new Item(new IID(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE), "Host Armor Trim Smithing Template"));
self::register("raiser_armor_trim_smithing_template", new Item(new IID(Ids::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE), "Raiser Armor Trim Smithing Template"));
self::register("rib_armor_trim_smithing_template", new Item(new IID(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE), "Rib Armor Trim Smithing Template"));
self::register("sentry_armor_trim_smithing_template", new Item(new IID(Ids::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE), "Sentry Armor Trim Smithing Template"));
self::register("shaper_armor_trim_smithing_template", new Item(new IID(Ids::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE), "Shaper Armor Trim Smithing Template"));
self::register("silence_armor_trim_smithing_template", new Item(new IID(Ids::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE), "Silence Armor Trim Smithing Template"));
self::register("snout_armor_trim_smithing_template", new Item(new IID(Ids::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE), "Snout Armor Trim Smithing Template"));
self::register("spire_armor_trim_smithing_template", new Item(new IID(Ids::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE), "Spire Armor Trim Smithing Template"));
self::register("tide_armor_trim_smithing_template", new Item(new IID(Ids::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE), "Tide Armor Trim Smithing Template"));
self::register("vex_armor_trim_smithing_template", new Item(new IID(Ids::VEX_ARMOR_TRIM_SMITHING_TEMPLATE), "Vex Armor Trim Smithing Template"));
self::register("ward_armor_trim_smithing_template", new Item(new IID(Ids::WARD_ARMOR_TRIM_SMITHING_TEMPLATE), "Ward Armor Trim Smithing Template"));
self::register("wayfinder_armor_trim_smithing_template", new Item(new IID(Ids::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE), "Wayfinder Armor Trim Smithing Template"));
self::register("wild_armor_trim_smithing_template", new Item(new IID(Ids::WILD_ARMOR_TRIM_SMITHING_TEMPLATE), "Wild Armor Trim Smithing Template"));
}
}

View File

@ -1,54 +0,0 @@
<?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\compression;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
class CompressBatchTask extends AsyncTask{
private const TLS_KEY_PROMISE = "promise";
/** @phpstan-var NonThreadSafeValue<Compressor> */
private NonThreadSafeValue $compressor;
public function __construct(
private string $data,
CompressBatchPromise $promise,
Compressor $compressor
){
$this->compressor = new NonThreadSafeValue($compressor);
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
}
public function onRun() : void{
$this->setResult($this->compressor->deserialize()->compress($this->data));
}
public function onCompletion() : void{
/** @var CompressBatchPromise $promise */
$promise = $this->fetchLocal(self::TLS_KEY_PROMISE);
$promise->resolve($this->getResult());
}
}

View File

@ -0,0 +1,209 @@
<?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\compression;
use pmmp\thread\ThreadSafeArray;
use pocketmine\snooze\SleeperHandler;
use pocketmine\snooze\SleeperHandlerEntry;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\Thread;
use pocketmine\utils\AssumptionFailedError;
use function count;
use function serialize;
use function unserialize;
final class CompressorWorker{
private Thread $thread;
/** @phpstan-var ThreadSafeArray<int, string|null> */
private ThreadSafeArray $inChannel;
/** @phpstan-var ThreadSafeArray<int, string> */
private ThreadSafeArray $outChannel;
/**
* @var CompressBatchPromise[]|\SplQueue
* @phpstan-var \SplQueue<CompressBatchPromise>
*/
private \SplQueue $promises;
private readonly int $sleeperNotifierId;
private bool $shutdown = false;
public function __construct(
Compressor $compressor,
private SleeperHandler $sleeperHandler,
){
$this->inChannel = new ThreadSafeArray();
$this->outChannel = new ThreadSafeArray();
$this->promises = new \SplQueue();
$sleeperEntry = $this->sleeperHandler->addNotifier(function() : void{
$this->processResults();
});
$this->sleeperNotifierId = $sleeperEntry->getNotifierId();
$this->thread = new class($this->inChannel, $this->outChannel, $compressor, $sleeperEntry) extends Thread{
private string $compressor;
/**
* @phpstan-param ThreadSafeArray<int, string|null> $inChannel
* @phpstan-param ThreadSafeArray<int, string> $outChannel
*/
public function __construct(
private ThreadSafeArray $inChannel,
private ThreadSafeArray $outChannel,
Compressor $compressor,
private SleeperHandlerEntry $sleeperEntry
){
$this->compressor = serialize($compressor);
}
public function onRun() : void{
/** @var Compressor $compressor */
$compressor = unserialize($this->compressor);
self::thread($this->inChannel, $compressor, $this->outChannel, $this->sleeperEntry->createNotifier());
}
/**
* @phpstan-param ThreadSafeArray<int, string|null> $inChannel
* @phpstan-param ThreadSafeArray<int, string> $outChannel
*/
private static function thread(ThreadSafeArray $inChannel, Compressor $compressor, ThreadSafeArray $outChannel, SleeperNotifier $sleeperNotifier) : void{
$shutdown = false;
while(!$shutdown){
$inBuffers = $inChannel->synchronized(function() use ($inChannel) : array{
while($inChannel->count() === 0){
$inChannel->wait();
}
/**
* @phpstan-var array<int, string|null> $result
* @var string[]|null[] $result
*/
$result = $inChannel->chunk(100, preserve: false);
return $result;
});
$outBuffers = [];
foreach($inBuffers as $inBuffer){
if($inBuffer === null){
$shutdown = true;
//don't break here - we still need to process the rest of the buffers
}else{
$outBuffers[] = $compressor->compress($inBuffer);
}
}
$outChannel->synchronized(function() use ($outChannel, $outBuffers) : void{
foreach($outBuffers as $outBuffer){
$outChannel[] = $outBuffer;
}
});
$sleeperNotifier->wakeupSleeper();
}
}
public function quit() : void{
$inChannel = $this->inChannel;
$inChannel->synchronized(function() use ($inChannel) : void{
$inChannel[] = null;
$inChannel->notify();
});
parent::quit();
}
};
$this->thread->setClassLoaders([]); //plugin class loaders are not needed here
$this->thread->start();
}
public function submit(string $buffer) : CompressBatchPromise{
if($this->shutdown){
throw new \LogicException("This worker has been shut down");
}
$this->inChannel->synchronized(function() use ($buffer) : void{
$this->inChannel[] = $buffer;
$this->inChannel->notify();
});
$promise = new CompressBatchPromise();
$this->promises->enqueue($promise);
return $promise;
}
/**
* @param string[] $buffers
* @return CompressBatchPromise[]
*/
public function submitBulk(array $buffers) : array{
if($this->shutdown){
throw new \LogicException("This worker has been shut down");
}
$this->inChannel->synchronized(function() use ($buffers) : void{
foreach($buffers as $buffer){
$this->inChannel[] = $buffer;
}
$this->inChannel->notify();
});
$promises = [];
foreach($buffers as $k => $buffer){
$promise = new CompressBatchPromise();
$this->promises->enqueue($promise);
$promises[$k] = $promise;
}
return $promises;
}
private function processResults() : int{
if(count($this->promises) === 0){
return 0;
}
do{
$results = $this->outChannel->synchronized(function() : array{
/** @var string[] $results */
$results = $this->outChannel->chunk(100, preserve: false);
return $results;
});
foreach($results as $compressed){
$promise = $this->promises->dequeue();
$promise->resolve($compressed);
}
}while(count($results) > 0);
return count($this->promises);
}
public function shutdown() : void{
$this->shutdown = true;
$this->thread->quit();
if($this->processResults() > 0){
throw new AssumptionFailedError("All compression work should have been done before shutdown");
}
$this->sleeperHandler->removeNotifier($this->sleeperNotifierId);
}
public function __destruct(){
$this->shutdown();
}
}

View File

@ -0,0 +1,91 @@
<?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\compression;
use pocketmine\snooze\SleeperHandler;
use function array_merge;
use function array_slice;
use function ceil;
use function count;
final class CompressorWorkerPool{
/**
* @var CompressorWorker[]
* @phpstan-var array<int, CompressorWorker>
*/
private array $workers = [];
private int $nextWorker = 0;
public function __construct(
private readonly int $maxSize,
private readonly Compressor $compressor,
private readonly SleeperHandler $sleeperHandler,
){}
public function getCompressor() : Compressor{ return $this->compressor; }
public function submit(string $buffer) : CompressBatchPromise{
$worker = $this->workers[$this->nextWorker] ?? null;
if($worker === null){
$worker = new CompressorWorker($this->compressor, $this->sleeperHandler);
$this->workers[$this->nextWorker] = $worker;
}
$this->nextWorker = ($this->nextWorker + 1) % $this->maxSize;
return $worker->submit($buffer);
}
/**
* @param string[] $buffers
* @return CompressBatchPromise[]
*/
public function submitBulk(array $buffers) : array{
$splitSize = (int) ceil(count($buffers) / $this->maxSize);
$results = [];
$offset = 0;
for($i = 0; $i < $this->maxSize; $i++){
$worker = $this->workers[$i] ??= new CompressorWorker($this->compressor, $this->sleeperHandler);
$results[] = $worker->submitBulk(array_slice($buffers, $offset, $splitSize, true));
$offset += $splitSize;
if($offset >= count($buffers)){
break;
}
}
return array_merge(...$results);
}
public function shutdown() : void{
foreach($this->workers as $worker){
$worker->shutdown();
}
$this->workers = [];
}
public function __destruct(){
$this->shutdown();
}
}

View File

@ -70,7 +70,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$this->configFile = Path::join($this->dataFolder, "config.yml");
$prefix = $this->getDescription()->getPrefix();
$prefix = $this->description->getPrefix();
$this->logger = new PluginLogger($server->getLogger(), $prefix !== "" ? $prefix : $this->getName());
$this->scheduler = new TaskScheduler($this->getFullName());
@ -145,9 +145,9 @@ abstract class PluginBase implements Plugin, CommandExecutor{
private function registerYamlCommands() : void{
$pluginCmds = [];
foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
foreach(Utils::stringifyKeys($this->description->getCommands()) as $key => $data){
if(str_contains($key, ":")){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":")));
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->description->getFullName(), ":")));
continue;
}
@ -163,7 +163,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$aliasList = [];
foreach($data->getAliases() as $alias){
if(str_contains($alias, ":")){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":")));
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->description->getFullName(), ":")));
continue;
}
$aliasList[] = $alias;
@ -181,7 +181,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
}
if(count($pluginCmds) > 0){
$this->server->getCommandMap()->registerAll($this->getDescription()->getName(), $pluginCmds);
$this->server->getCommandMap()->registerAll($this->description->getName(), $pluginCmds);
}
}
@ -190,9 +190,9 @@ abstract class PluginBase implements Plugin, CommandExecutor{
* @phpstan-return (Command&PluginOwned)|null
*/
public function getCommand(string $name){
$command = $this->getServer()->getPluginCommand($name);
$command = $this->server->getPluginCommand($name);
if($command === null || $command->getOwningPlugin() !== $this){
$command = $this->getServer()->getPluginCommand(strtolower($this->description->getName()) . ":" . $name);
$command = $this->server->getPluginCommand(strtolower($this->description->getName()) . ":" . $name);
}
if($command instanceof PluginOwned && $command->getOwningPlugin() === $this){

View File

@ -35,6 +35,7 @@ use function get_class;
use function str_starts_with;
abstract class Timings{
public const GROUP_MINECRAFT = "Minecraft";
public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
private static bool $initialized = false;
@ -134,8 +135,8 @@ abstract class Timings{
self::$initialized = true;
self::$fullTick = new TimingsHandler("Full Server Tick");
self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick, group: self::GROUP_BREAKDOWN);
self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick, group: self::GROUP_BREAKDOWN);
self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick);
self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick);
self::$memoryManager = new TimingsHandler("Memory Manager");
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
self::$titleTick = new TimingsHandler("Console Title Tick");
@ -143,51 +144,51 @@ abstract class Timings{
self::$connection = new TimingsHandler("Connection Handler");
self::$playerNetworkSend = new TimingsHandler("Player Network Send", self::$connection);
self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend);
self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress);
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress);
self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend);
self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend);
self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend);
self::$playerNetworkReceive = new TimingsHandler("Player Network Receive", self::$connection);
self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive);
self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive);
self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend);
self::$playerMove = new TimingsHandler("Player Movement");
self::$playerChunkOrder = new TimingsHandler("Player Order Chunks");
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend);
self::$scheduler = new TimingsHandler("Scheduler");
self::$serverCommand = new TimingsHandler("Server Command");
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation);
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation);
self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load");
self::$syncPlayerDataSave = new TimingsHandler("Player Data Save");
self::$entityMove = new TimingsHandler("Entity Movement", group: self::GROUP_BREAKDOWN);
self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove, group: self::GROUP_BREAKDOWN);
self::$entityMove = new TimingsHandler("Entity Movement");
self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove);
self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove, group: self::GROUP_BREAKDOWN);
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove);
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove);
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities");
self::$entityBaseTick = new TimingsHandler("Entity Base Tick");
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living");
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity");
self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN);
self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks");
self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN);
self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks");
self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync);
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync);
self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN);
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN);
self::$playerCommand = new TimingsHandler("Player Command");
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");
}
@ -229,7 +230,7 @@ abstract class Timings{
}else{
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
}
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN);
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName);
}
return self::$entityTypeTimingMap[$entity::class];
@ -239,8 +240,7 @@ abstract class Timings{
self::init();
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
group: self::GROUP_BREAKDOWN
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\")
);
}
@ -250,7 +250,7 @@ abstract class Timings{
public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
self::init();
if(!isset(self::$packetReceiveTimingMap[$pk::class])){
self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive);
}
return self::$packetReceiveTimingMap[$pk::class];
@ -259,31 +259,28 @@ abstract class Timings{
public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
"Decode - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
self::getReceiveDataPacketTimings($pk)
);
}
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
"Handler - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
self::getReceiveDataPacketTimings($pk)
);
}
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
"Encode - " . $pk->getName(),
self::getSendDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
self::getSendDataPacketTimings($pk)
);
}
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
self::init();
if(!isset(self::$packetSendTimingMap[$pk::class])){
self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend);
}
return self::$packetSendTimingMap[$pk::class];
@ -292,7 +289,7 @@ abstract class Timings{
public static function getCommandDispatchTimings(string $commandName) : TimingsHandler{
self::init();
return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName, group: self::GROUP_BREAKDOWN);
return self::$commandTimingMap[$commandName] ??= new TimingsHandler("Command - " . $commandName);
}
public static function getEventTimings(Event $event) : TimingsHandler{
@ -316,7 +313,7 @@ abstract class Timings{
return self::$eventHandlers[$event][$handlerName];
}
public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskProgressUpdate[$taskClass])){
self::init();
@ -330,7 +327,7 @@ abstract class Timings{
return self::$asyncTaskProgressUpdate[$taskClass];
}
public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskCompletion[$taskClass])){
self::init();
@ -344,7 +341,7 @@ abstract class Timings{
return self::$asyncTaskCompletion[$taskClass];
}
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskError[$taskClass])){
self::init();

View File

@ -120,7 +120,7 @@ class TimingsHandler{
public function __construct(
private string $name,
private ?TimingsHandler $parent = null,
private string $group = "Minecraft"
private string $group = Timings::GROUP_MINECRAFT
){}
public function getName() : string{ return $this->name; }

View File

@ -1666,16 +1666,19 @@ class World implements ChunkManager{
}
/**
* Returns the highest available level of any type of light at the given coordinates, adjusted for the current
* weather and time of day.
* Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and
* time of day.
*/
public function getFullLight(Vector3 $pos) : int{
return $this->getFullLightAt($pos->x, $pos->y, $pos->z);
$floorX = $pos->getFloorX();
$floorY = $pos->getFloorY();
$floorZ = $pos->getFloorZ();
return $this->getFullLightAt($floorX, $floorY, $floorZ);
}
/**
* Returns the highest available level of any type of light at the given coordinates, adjusted for the current
* weather and time of day.
* Returns the highest level of any type of light at the given coordinates, adjusted for the current weather and
* time of day.
*/
public function getFullLightAt(int $x, int $y, int $z) : int{
$skyLight = $this->getRealBlockSkyLightAt($x, $y, $z);
@ -1687,18 +1690,43 @@ class World implements ChunkManager{
}
/**
* Returns the highest available level of any type of light at, or adjacent to, the given coordinates, adjusted for
* the current weather and time of day.
* Returns the highest level of any type of light at, or adjacent to, the given coordinates, adjusted for the
* current weather and time of day.
*/
public function getHighestAdjacentFullLightAt(int $x, int $y, int $z) : int{
return $this->getHighestAdjacentLight($x, $y, $z, $this->getFullLightAt(...));
}
/**
* Returns the highest potential level of any type of light at the target coordinates.
* This is not affected by weather or time of day.
*/
public function getPotentialLight(Vector3 $pos) : int{
$floorX = $pos->getFloorX();
$floorY = $pos->getFloorY();
$floorZ = $pos->getFloorZ();
return $this->getPotentialLightAt($floorX, $floorY, $floorZ);
}
/**
* Returns the highest potential level of any type of light at the target coordinates.
* This is not affected by weather or time of day.
*/
public function getPotentialLightAt(int $x, int $y, int $z) : int{
return max($this->getPotentialBlockSkyLightAt($x, $y, $z), $this->getBlockLightAt($x, $y, $z));
}
/**
* Returns the highest potential level of any type of light at, or adjacent to, the given coordinates.
* This is not affected by weather or time of day.
*/
public function getHighestAdjacentPotentialLightAt(int $x, int $y, int $z) : int{
return $this->getHighestAdjacentLight($x, $y, $z, $this->getPotentialLightAt(...));
}
/**
* Returns the highest potential level of sky light at the target coordinates, regardless of the time of day or
* weather conditions.
* You usually don't want to use this for vanilla gameplay logic; prefer the real sky light instead.
* @see World::getRealBlockSkyLightAt()
*
* @return int 0-15
*/

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\world;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
class WorldTimings{
@ -66,7 +65,7 @@ class WorldTimings{
private static function newTimer(string $worldName, string $timerName) : TimingsHandler{
$aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table
return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN);
return new TimingsHandler("$worldName - $timerName", $aggregator);
}
public function __construct(World $world){

View File

@ -425,6 +425,21 @@ parameters:
count: 3
path: ../../../src/block/tile/Spawnable.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/utils/CropGrowthHelper.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/utils/CropGrowthHelper.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getPotentialLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/utils/CropGrowthHelper.php
-
message: "#^Cannot call method addParticle\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#"
count: 1
@ -910,11 +925,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@ -940,11 +950,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@ -975,11 +980,6 @@ parameters:
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/world/World.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#"
count: 1