mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 00:07:30 +00:00
Merge branch 'next-minor' into stable
This commit is contained in:
commit
3dd1a14fb7
@ -36,8 +36,8 @@ use function ksort;
|
||||
use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function str_ends_with;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
use const SORT_STRING;
|
||||
use const STDERR;
|
||||
|
||||
@ -121,7 +121,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
if(is_dir($argv[1])){
|
||||
/** @var string $file */
|
||||
foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
|
||||
if(substr($file, -4) !== ".php"){
|
||||
if(!str_ends_with($file, ".php")){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\make_release;
|
||||
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\VersionString;
|
||||
use pocketmine\VersionInfo;
|
||||
@ -30,7 +31,6 @@ use function array_keys;
|
||||
use function array_map;
|
||||
use function dirname;
|
||||
use function fgets;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function fwrite;
|
||||
use function getopt;
|
||||
@ -50,7 +50,7 @@ use const STR_PAD_LEFT;
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev, string $channel) : void{
|
||||
$versionInfo = Utils::assumeNotFalse(file_get_contents($versionInfoPath), $versionInfoPath . " should always exist");
|
||||
$versionInfo = Filesystem::fileGetContents($versionInfoPath);
|
||||
$versionInfo = preg_replace(
|
||||
$pattern = '/^([\t ]*public )?const BASE_VERSION = "(\d+)\.(\d+)\.(\d+)(?:-(.*))?";$/m',
|
||||
'$1const BASE_VERSION = "' . $newVersion . '";',
|
||||
|
94
changelogs/4.13-beta.md
Normal file
94
changelogs/4.13-beta.md
Normal file
@ -0,0 +1,94 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.50**
|
||||
|
||||
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.13.0-BETA1
|
||||
Released 18th January 2023.
|
||||
|
||||
## Gameplay
|
||||
- Death message is now shown on the death screen when a player dies.
|
||||
- Armour damage is now only increased if the armour reduced the damage taken.
|
||||
- Implemented Swift Sneak enchantment.
|
||||
- Fixed incorrect collision box calculation of walls and glass/bars when connected. Note: Client-side, wall connections are still broken; this only fixes projectile flight server-side.
|
||||
|
||||
## Performance
|
||||
- Improved performance of chunk selection for chunk random ticking using a cache. This improves performance of chunk random ticking by 10-20%.
|
||||
|
||||
## Localization
|
||||
- Added localized description for the `/dumpmemory` command.
|
||||
|
||||
## Permissions
|
||||
- Added the following new core permissions:
|
||||
- `pocketmine.command.effect.other` - allows the player to use the `/effect` command on other players (default operator only)
|
||||
- `pocketmine.command.effect.self` - allows the player to use the `/effect` command on themselves (default operator only)
|
||||
- `pocketmine.command.enchant.other` - allows the player to use the `/enchant` command on other players (default operator only)
|
||||
- `pocketmine.command.enchant.self` - allows the player to use the `/enchant` command on themselves (default operator only)
|
||||
- `pocketmine.command.gamemode.other` - allows the player to use the `/gamemode` command on other players (default operator only)
|
||||
- `pocketmine.command.gamemode.self` - allows the player to use the `/gamemode` command on themselves (default operator only)
|
||||
- `pocketmine.command.give.other` - allows the player to use the `/give` command on other players (default operator only)
|
||||
- `pocketmine.command.give.self` - allows the player to use the `/give` command on themselves (default operator only)
|
||||
- `pocketmine.command.spawnpoint.other` - allows the player to use the `/spawnpoint` command on other players (default operator only)
|
||||
- `pocketmine.command.spawnpoint.self` - allows the player to use the `/spawnpoint` command on themselves (default operator only)
|
||||
- `pocketmine.command.teleport.other` - allows the player to use the `/teleport` command on other players (default operator only)
|
||||
- `pocketmine.command.teleport.self` - allows the player to use the `/teleport` command on themselves (default operator only)
|
||||
- `pocketmine.command.title.other` - allows the player to use the `/title` command on other players (default operator only)
|
||||
- `pocketmine.command.title.self` - allows the player to use the `/title` command on themselves (default operator only)
|
||||
|
||||
## Internals
|
||||
- Decoupled `Player->sendMessage()` and `Player->sendTranslation()`.
|
||||
- Refactored resource pack loading in `ResourcePackManager` to make it easier to understand.
|
||||
- Client-aware translation processing has been moved to `NetworkSession` due to being client-specific.
|
||||
- Replaced hardcoded strings with constants in various places.
|
||||
- `NetworkSession` destructive cleanup is now deferred to the next session tick. This fixes various `InventoryManager` crashes when kicking players during events.
|
||||
- Updated code using `strpos()` to use `str_starts_with()`, `str_ends_with()` and `str_contains()` where appropriate.
|
||||
- Added documentation for some internal methods.
|
||||
|
||||
## API
|
||||
### `pocketmine\command`
|
||||
- The following new API methods have been added:
|
||||
- `protected VanillaCommand->fetchPermittedPlayerTarget(...) : ?Player` - fetches a player target according to the given sender permissions, or null if not found or not permitted
|
||||
|
||||
### `pocketmine\entity`
|
||||
- The following new API methods have been added:
|
||||
- `public Living->getDisplayName() : string` - the name of the entity to be shown in death messages, commands etc.
|
||||
|
||||
### `pocketmine\event\world`
|
||||
- The following new classes have been added:
|
||||
- `WorldSoundEvent` - called when a sound is played in a world
|
||||
- `WorldParticleEvent` - called when a particle is spawned in a world
|
||||
|
||||
### `pocketmine\item`
|
||||
- The following new API methods have been added:
|
||||
- `public Item->onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool` - called when a player interacts with an entity with this item in hand
|
||||
|
||||
### `pocketmine\lang`
|
||||
- `Language->translate()` and `Language->translateString()` no longer parse nested translation in the "base text". This was never intended behaviour, and didn't work beyond the first level anyway.
|
||||
|
||||
### `pocketmine\player`
|
||||
- The following new interfaces have been added:
|
||||
- `PlayerDataProvider` - implemented by classes which want to offer storage for player data
|
||||
- The following new classes have been added:
|
||||
- `DatFilePlayerDataProvider` - the default player data provider, which stores `.dat` files in the `players` folder
|
||||
- `PlayerDataLoadException` - thrown when an error occurs while loading player data
|
||||
- `PlayerDataSaveException` - thrown when an error occurs while saving player data
|
||||
- The following API methods have been deprecated:
|
||||
- `Player->sendTranslation()` - use `Player->sendMessage()` instead with a `Translatable` message
|
||||
|
||||
### `pocketmine\resourcepacks`
|
||||
- The following new API methods have been added:
|
||||
- `public ResourcePackManager->setResourceStack(list<ResourcePack> $resourceStack) : void` - sets the list of resource packs to be applied by players
|
||||
- `public ResourcePackManager->setPackEncryptionKey(string $id, ?string $key) : void` - sets the encryption key to be used for a resource pack
|
||||
|
||||
### `pocketmine\utils`
|
||||
- The following new API methods have been added:
|
||||
- `public static Filesystem::fileGetContents(...) : string` - a wrapper around `file_get_contents()` which throws an exception on failure
|
||||
|
||||
### `pocketmine\world`
|
||||
- The following new API methods have been added:
|
||||
- `public World->requestSafeSpawn(?Vector3 $spawn = null) : Promise<Position>` - an async version of `getSafeSpawn()` which generates all the needed chunks before returning
|
@ -35,13 +35,13 @@
|
||||
"fgrosse/phpasn1": "^2.3",
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
|
||||
"pocketmine/bedrock-protocol": "~17.1.0+bedrock-1.19.50",
|
||||
"pocketmine/bedrock-protocol": "~18.1.0+bedrock-1.19.50",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/classloader": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/locale-data": "~2.11.0",
|
||||
"pocketmine/locale-data": "~2.18.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.4.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
|
48
composer.lock
generated
48
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8e2b03d128d6c14cf0f5e78d4bb7af90",
|
||||
"content-hash": "2c0f273b515174abfdcef4fc4ad3c262",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -276,16 +276,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "17.1.0+bedrock-1.19.50",
|
||||
"version": "18.1.0+bedrock-1.19.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de"
|
||||
"reference": "c34f22847a1cc362a3f1c45698576d30d1e8392f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c572706cf5e3202718dd35a35dd30fe08cd671de",
|
||||
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c34f22847a1cc362a3f1c45698576d30d1e8392f",
|
||||
"reference": "c34f22847a1cc362a3f1c45698576d30d1e8392f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -293,13 +293,13 @@
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"php": "^8.0",
|
||||
"pocketmine/binaryutils": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0 || ^0.3.0",
|
||||
"pocketmine/math": "^0.3.0 || ^0.4.0",
|
||||
"pocketmine/nbt": "^0.3.0",
|
||||
"ramsey/uuid": "^4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.9.3",
|
||||
"phpstan/phpstan": "1.9.13",
|
||||
"phpstan/phpstan-phpunit": "^1.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
@ -317,9 +317,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/17.1.0+bedrock-1.19.50"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/18.1.0+bedrock-1.19.50"
|
||||
},
|
||||
"time": "2022-12-15T20:34:49+00:00"
|
||||
"time": "2023-01-20T12:35:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -460,24 +460,24 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/color",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Color.git",
|
||||
"reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2"
|
||||
"reference": "8cb346d0a21ad3287cc8d7175e4b643416607249"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Color/zipball/09be6ea6d76f2e33d6813c39d29c22c46c17e1d2",
|
||||
"reference": "09be6ea6d76f2e33d6813c39d29c22c46c17e1d2",
|
||||
"url": "https://api.github.com/repos/pmmp/Color/zipball/8cb346d0a21ad3287cc8d7175e4b643416607249",
|
||||
"reference": "8cb346d0a21ad3287cc8d7175e4b643416607249",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "0.12.59",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2"
|
||||
"phpstan/phpstan": "1.9.4",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -492,9 +492,9 @@
|
||||
"description": "Color handling library used by PocketMine-MP and related projects",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Color/issues",
|
||||
"source": "https://github.com/pmmp/Color/tree/0.2.0"
|
||||
"source": "https://github.com/pmmp/Color/tree/0.3.0"
|
||||
},
|
||||
"time": "2020-12-11T01:24:32+00:00"
|
||||
"time": "2022-12-18T19:49:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/errorhandler",
|
||||
@ -537,16 +537,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.11.0",
|
||||
"version": "2.18.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "4b33d8fa53eda53d9662a7478806ebae2e4a5c53"
|
||||
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/4b33d8fa53eda53d9662a7478806ebae2e4a5c53",
|
||||
"reference": "4b33d8fa53eda53d9662a7478806ebae2e4a5c53",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
|
||||
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -554,9 +554,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.11.0"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.18.3"
|
||||
},
|
||||
"time": "2022-11-25T14:24:34+00:00"
|
||||
"time": "2023-01-17T21:43:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
|
@ -69,6 +69,10 @@ use const JSON_UNESCAPED_SLASHES;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
class MemoryManager{
|
||||
private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND;
|
||||
private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2;
|
||||
private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND;
|
||||
|
||||
private int $memoryLimit;
|
||||
private int $globalMemoryLimit;
|
||||
private int $checkRate;
|
||||
@ -113,20 +117,12 @@ class MemoryManager{
|
||||
if($m <= 0){
|
||||
$defaultMemory = 0;
|
||||
}else{
|
||||
switch(mb_strtoupper($matches[2])){
|
||||
case "K":
|
||||
$defaultMemory = intdiv($m, 1024);
|
||||
break;
|
||||
case "M":
|
||||
$defaultMemory = $m;
|
||||
break;
|
||||
case "G":
|
||||
$defaultMemory = $m * 1024;
|
||||
break;
|
||||
default:
|
||||
$defaultMemory = $m;
|
||||
break;
|
||||
}
|
||||
$defaultMemory = match(mb_strtoupper($matches[2])){
|
||||
"K" => intdiv($m, 1024),
|
||||
"M" => $m,
|
||||
"G" => $m * 1024,
|
||||
default => $m,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,11 +135,11 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$this->globalMemoryLimit = $config->getPropertyInt("memory.global-limit", 0) * 1024 * 1024;
|
||||
$this->checkRate = $config->getPropertyInt("memory.check-rate", 20);
|
||||
$this->checkRate = $config->getPropertyInt("memory.check-rate", self::DEFAULT_CHECK_RATE);
|
||||
$this->continuousTrigger = $config->getPropertyBool("memory.continuous-trigger", true);
|
||||
$this->continuousTriggerRate = $config->getPropertyInt("memory.continuous-trigger-rate", 30);
|
||||
$this->continuousTriggerRate = $config->getPropertyInt("memory.continuous-trigger-rate", self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
|
||||
|
||||
$this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", 36000);
|
||||
$this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", self::DEFAULT_TICKS_PER_GC);
|
||||
$this->garbageCollectionTrigger = $config->getPropertyBool("memory.garbage-collection.low-memory-trigger", true);
|
||||
$this->garbageCollectionAsync = $config->getPropertyBool("memory.garbage-collection.collect-async-worker", true);
|
||||
|
||||
|
190
src/Server.php
190
src/Server.php
@ -49,10 +49,7 @@ use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Language;
|
||||
use pocketmine\lang\LanguageNotFoundException;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\nbt\BigEndianNbtSerializer;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\compression\CompressBatchPromise;
|
||||
use pocketmine\network\mcpe\compression\CompressBatchTask;
|
||||
use pocketmine\network\mcpe\compression\Compressor;
|
||||
@ -72,9 +69,13 @@ use pocketmine\network\query\QueryInfo;
|
||||
use pocketmine\network\upnp\UPnPNetworkInterface;
|
||||
use pocketmine\permission\BanList;
|
||||
use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\player\DatFilePlayerDataProvider;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\OfflinePlayer;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\player\PlayerDataLoadException;
|
||||
use pocketmine\player\PlayerDataProvider;
|
||||
use pocketmine\player\PlayerDataSaveException;
|
||||
use pocketmine\player\PlayerInfo;
|
||||
use pocketmine\plugin\PharPluginLoader;
|
||||
use pocketmine\plugin\Plugin;
|
||||
@ -105,17 +106,18 @@ use pocketmine\utils\SignalHandler;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\WorldProviderManager;
|
||||
use pocketmine\world\format\io\WritableWorldProviderManagerEntry;
|
||||
use pocketmine\world\generator\Generator;
|
||||
use pocketmine\world\generator\GeneratorManager;
|
||||
use pocketmine\world\generator\InvalidGeneratorOptionsException;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\world\WorldCreationOptions;
|
||||
use pocketmine\world\WorldManager;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_fill;
|
||||
use function array_sum;
|
||||
use function base64_encode;
|
||||
use function cli_set_process_title;
|
||||
@ -124,7 +126,6 @@ use function count;
|
||||
use function date;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function fopen;
|
||||
@ -161,12 +162,9 @@ use function time;
|
||||
use function touch;
|
||||
use function trim;
|
||||
use function yaml_parse;
|
||||
use function zlib_decode;
|
||||
use function zlib_encode;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const PHP_EOL;
|
||||
use const PHP_INT_MAX;
|
||||
use const ZLIB_ENCODING_GZIP;
|
||||
|
||||
/**
|
||||
* The class that manages everything
|
||||
@ -184,6 +182,27 @@ class Server{
|
||||
public const DEFAULT_PORT_IPV6 = 19133;
|
||||
public const DEFAULT_MAX_VIEW_DISTANCE = 16;
|
||||
|
||||
/**
|
||||
* Worlds, network, commands and most other things are polled this many times per second on average.
|
||||
* Between ticks, the server will sleep to ensure that the average tick rate is maintained.
|
||||
* It may wake up between ticks if a Snooze notification source is triggered (e.g. to process network packets).
|
||||
*/
|
||||
public const TARGET_TICKS_PER_SECOND = 20;
|
||||
/**
|
||||
* The average time between ticks, in seconds.
|
||||
*/
|
||||
public const TARGET_SECONDS_PER_TICK = 1 / self::TARGET_TICKS_PER_SECOND;
|
||||
public const TARGET_NANOSECONDS_PER_TICK = 1_000_000_000 / self::TARGET_TICKS_PER_SECOND;
|
||||
|
||||
/**
|
||||
* The TPS threshold below which the server will generate log warnings.
|
||||
*/
|
||||
private const TPS_OVERLOAD_WARNING_THRESHOLD = self::TARGET_TICKS_PER_SECOND * 0.6;
|
||||
|
||||
private const TICKS_PER_WORLD_CACHE_CLEAR = 5 * self::TARGET_TICKS_PER_SECOND;
|
||||
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 static ?Server $instance = null;
|
||||
|
||||
private TimeTrackingSleeperHandler $tickSleeper;
|
||||
@ -202,7 +221,7 @@ class Server{
|
||||
|
||||
private PluginManager $pluginManager;
|
||||
|
||||
private float $profilingTickRate = 20;
|
||||
private float $profilingTickRate = self::TARGET_TICKS_PER_SECOND;
|
||||
|
||||
private UpdateChecker $updater;
|
||||
|
||||
@ -212,10 +231,10 @@ class Server{
|
||||
private int $tickCounter = 0;
|
||||
private float $nextTick = 0;
|
||||
/** @var float[] */
|
||||
private array $tickAverage = [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20];
|
||||
private array $tickAverage;
|
||||
/** @var float[] */
|
||||
private array $useAverage = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
private float $currentTPS = 20;
|
||||
private array $useAverage;
|
||||
private float $currentTPS = self::TARGET_TICKS_PER_SECOND;
|
||||
private float $currentUse = 0;
|
||||
private float $startTime;
|
||||
|
||||
@ -251,6 +270,8 @@ class Server{
|
||||
private string $dataPath;
|
||||
private string $pluginPath;
|
||||
|
||||
private PlayerDataProvider $playerDataProvider;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<string, string>
|
||||
@ -484,49 +505,22 @@ class Server{
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getPlayerDataPath(string $username) : string{
|
||||
return Path::join($this->getDataPath(), 'players', strtolower($username) . '.dat');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the server has stored any saved data for this player.
|
||||
*/
|
||||
public function hasOfflinePlayerData(string $name) : bool{
|
||||
return file_exists($this->getPlayerDataPath($name));
|
||||
}
|
||||
|
||||
private function handleCorruptedPlayerData(string $name) : void{
|
||||
$path = $this->getPlayerDataPath($name);
|
||||
rename($path, $path . '.bak');
|
||||
$this->logger->error($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
|
||||
return $this->playerDataProvider->hasData($name);
|
||||
}
|
||||
|
||||
public function getOfflinePlayerData(string $name) : ?CompoundTag{
|
||||
return Timings::$syncPlayerDataLoad->time(function() use ($name) : ?CompoundTag{
|
||||
$name = strtolower($name);
|
||||
$path = $this->getPlayerDataPath($name);
|
||||
|
||||
if(file_exists($path)){
|
||||
$contents = @file_get_contents($path);
|
||||
if($contents === false){
|
||||
throw new \RuntimeException("Failed to read player data file \"$path\" (permission denied?)");
|
||||
}
|
||||
$decompressed = @zlib_decode($contents);
|
||||
if($decompressed === false){
|
||||
$this->logger->debug("Failed to decompress raw player data for \"$name\"");
|
||||
$this->handleCorruptedPlayerData($name);
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
return (new BigEndianNbtSerializer())->read($decompressed)->mustGetCompoundTag();
|
||||
}catch(NbtDataException $e){ //corrupt data
|
||||
$this->logger->debug("Failed to decode NBT data for \"$name\": " . $e->getMessage());
|
||||
$this->handleCorruptedPlayerData($name);
|
||||
return null;
|
||||
}
|
||||
try{
|
||||
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)));
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@ -540,11 +534,9 @@ class Server{
|
||||
|
||||
if(!$ev->isCancelled()){
|
||||
Timings::$syncPlayerDataSave->time(function() use ($name, $ev) : void{
|
||||
$nbt = new BigEndianNbtSerializer();
|
||||
$contents = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot($ev->getSaveData())), ZLIB_ENCODING_GZIP), "zlib_encode() failed unexpectedly");
|
||||
try{
|
||||
Filesystem::safeFilePutContents($this->getPlayerDataPath($name), $contents);
|
||||
}catch(\RuntimeException $e){
|
||||
$this->playerDataProvider->saveData($name, $ev->getSaveData());
|
||||
}catch(PlayerDataSaveException $e){
|
||||
$this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
|
||||
$this->logger->logException($e);
|
||||
}
|
||||
@ -560,51 +552,47 @@ class Server{
|
||||
$ev->call();
|
||||
$class = $ev->getPlayerClass();
|
||||
|
||||
if($offlinePlayerData !== null && ($world = $this->worldManager->getWorldByName($offlinePlayerData->getString("Level", ""))) !== null){
|
||||
if($offlinePlayerData !== null && ($world = $this->worldManager->getWorldByName($offlinePlayerData->getString(Player::TAG_LEVEL, ""))) !== null){
|
||||
$playerPos = EntityDataHelper::parseLocation($offlinePlayerData, $world);
|
||||
$spawn = $playerPos->asVector3();
|
||||
}else{
|
||||
$world = $this->worldManager->getDefaultWorld();
|
||||
if($world === null){
|
||||
throw new AssumptionFailedError("Default world should always be loaded");
|
||||
}
|
||||
$playerPos = null;
|
||||
$spawn = $world->getSpawnLocation();
|
||||
}
|
||||
/** @phpstan-var PromiseResolver<Player> $playerPromiseResolver */
|
||||
$playerPromiseResolver = new PromiseResolver();
|
||||
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
|
||||
if(!$session->isConnected()){
|
||||
$playerPromiseResolver->reject();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Stick with the original spawn at the time of generation request, even if it changed since then.
|
||||
* This is because we know for sure that that chunk will be generated, but the one at the new location
|
||||
* might not be, and it would be much more complex to go back and redo the whole thing.
|
||||
*
|
||||
* TODO: this relies on the assumption that getSafeSpawn() will only alter the Y coordinate of the
|
||||
* provided position. If this assumption is broken, we'll start seeing crashes in here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see Player::__construct()
|
||||
* @var Player $player
|
||||
*/
|
||||
$player = new $class($this, $session, $playerInfo, $authenticated, $playerPos ?? Location::fromObject($world->getSafeSpawn($spawn), $world), $offlinePlayerData);
|
||||
if(!$player->hasPlayedBefore()){
|
||||
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move
|
||||
}
|
||||
$playerPromiseResolver->resolve($player);
|
||||
},
|
||||
static function() use ($playerPromiseResolver, $session) : void{
|
||||
if($session->isConnected()){
|
||||
$session->disconnect("Spawn terrain generation failed");
|
||||
}
|
||||
$playerPromiseResolver->reject();
|
||||
$createPlayer = function(Location $location) use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $offlinePlayerData) : void{
|
||||
$player = new $class($this, $session, $playerInfo, $authenticated, $location, $offlinePlayerData);
|
||||
if(!$player->hasPlayedBefore()){
|
||||
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move
|
||||
}
|
||||
);
|
||||
$playerPromiseResolver->resolve($player);
|
||||
};
|
||||
|
||||
if($playerPos === null){ //new player or no valid position due to world not being loaded
|
||||
$world->requestSafeSpawn()->onCompletion(
|
||||
function(Position $spawn) use ($createPlayer, $playerPromiseResolver, $session, $world) : void{
|
||||
if(!$session->isConnected()){
|
||||
$playerPromiseResolver->reject();
|
||||
return;
|
||||
}
|
||||
$createPlayer(Location::fromObject($spawn, $world));
|
||||
},
|
||||
function() use ($playerPromiseResolver, $session) : void{
|
||||
if($session->isConnected()){
|
||||
//TODO: this needs to be localized - this might be reached if the spawn world was unloaded while the player was logging in
|
||||
$session->disconnect("Failed to find a safe spawn location");
|
||||
}
|
||||
$playerPromiseResolver->reject();
|
||||
}
|
||||
);
|
||||
}else{ //returning player with a valid position - safe spawn not required
|
||||
$createPlayer($playerPos);
|
||||
}
|
||||
|
||||
return $playerPromiseResolver->getPromise();
|
||||
}
|
||||
|
||||
@ -780,6 +768,8 @@ class Server{
|
||||
}
|
||||
self::$instance = $this;
|
||||
$this->startTime = microtime(true);
|
||||
$this->tickAverage = array_fill(0, self::TARGET_TICKS_PER_SECOND, self::TARGET_TICKS_PER_SECOND);
|
||||
$this->useAverage = array_fill(0, self::TARGET_TICKS_PER_SECOND, 0);
|
||||
|
||||
Timings::init();
|
||||
$this->tickSleeper = new TimeTrackingSleeperHandler(Timings::$serverInterrupts);
|
||||
@ -807,7 +797,7 @@ class Server{
|
||||
$this->logger->info("Loading server configuration");
|
||||
$pocketmineYmlPath = Path::join($this->dataPath, "pocketmine.yml");
|
||||
if(!file_exists($pocketmineYmlPath)){
|
||||
$content = Utils::assumeNotFalse(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "pocketmine.yml")), "Missing required resource file");
|
||||
$content = Filesystem::fileGetContents(Path::join(\pocketmine\RESOURCE_PATH, "pocketmine.yml"));
|
||||
if(VersionInfo::IS_DEVELOPMENT_BUILD){
|
||||
$content = str_replace("preferred-channel: stable", "preferred-channel: beta", $content);
|
||||
}
|
||||
@ -963,7 +953,7 @@ class Server{
|
||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
|
||||
|
||||
TimingsHandler::setEnabled($this->configGroup->getPropertyBool("settings.enable-profiling", false));
|
||||
$this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", 20);
|
||||
$this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", self::TARGET_TICKS_PER_SECOND);
|
||||
|
||||
DefaultPermissions::registerCorePermissions();
|
||||
|
||||
@ -979,7 +969,7 @@ class Server{
|
||||
copy(Path::join(\pocketmine\RESOURCE_PATH, 'plugin_list.yml'), $graylistFile);
|
||||
}
|
||||
try{
|
||||
$pluginGraylist = PluginGraylist::fromArray(yaml_parse(file_get_contents($graylistFile)));
|
||||
$pluginGraylist = PluginGraylist::fromArray(yaml_parse(Filesystem::fileGetContents($graylistFile)));
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$this->logger->emergency("Failed to load $graylistFile: " . $e->getMessage());
|
||||
$this->forceShutdownExit();
|
||||
@ -1001,12 +991,14 @@ class Server{
|
||||
|
||||
$this->worldManager = new WorldManager($this, Path::join($this->dataPath, "worlds"), $providerManager);
|
||||
$this->worldManager->setAutoSave($this->configGroup->getConfigBool("auto-save", $this->worldManager->getAutoSave()));
|
||||
$this->worldManager->setAutoSaveInterval($this->configGroup->getPropertyInt("ticks-per.autosave", 6000));
|
||||
$this->worldManager->setAutoSaveInterval($this->configGroup->getPropertyInt("ticks-per.autosave", $this->worldManager->getAutoSaveInterval()));
|
||||
|
||||
$this->updater = new UpdateChecker($this, $this->configGroup->getPropertyString("auto-updater.host", "update.pmmp.io"));
|
||||
|
||||
$this->queryInfo = new QueryInfo($this);
|
||||
|
||||
$this->playerDataProvider = new DatFilePlayerDataProvider(Path::join($this->dataPath, "players"));
|
||||
|
||||
register_shutdown_function([$this, "crashDump"]);
|
||||
|
||||
$loadErrorCount = 0;
|
||||
@ -1039,7 +1031,7 @@ class Server{
|
||||
}
|
||||
|
||||
if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
|
||||
$this->sendUsageTicker = 6000;
|
||||
$this->sendUsageTicker = self::TICKS_PER_STATS_REPORT;
|
||||
$this->sendUsage(SendUsageTask::TYPE_OPEN);
|
||||
}
|
||||
|
||||
@ -1821,11 +1813,11 @@ class Server{
|
||||
$this->network->tick();
|
||||
Timings::$connection->stopTiming();
|
||||
|
||||
if(($this->tickCounter % 20) === 0){
|
||||
if(($this->tickCounter % self::TARGET_TICKS_PER_SECOND) === 0){
|
||||
if($this->doTitleTick){
|
||||
$this->titleTick();
|
||||
}
|
||||
$this->currentTPS = 20;
|
||||
$this->currentTPS = self::TARGET_TICKS_PER_SECOND;
|
||||
$this->currentUse = 0;
|
||||
|
||||
$queryRegenerateEvent = new QueryRegenerateEvent(new QueryInfo($this));
|
||||
@ -1837,18 +1829,18 @@ class Server{
|
||||
}
|
||||
|
||||
if($this->sendUsageTicker > 0 && --$this->sendUsageTicker === 0){
|
||||
$this->sendUsageTicker = 6000;
|
||||
$this->sendUsageTicker = self::TICKS_PER_STATS_REPORT;
|
||||
$this->sendUsage(SendUsageTask::TYPE_STATUS);
|
||||
}
|
||||
|
||||
if(($this->tickCounter % 100) === 0){
|
||||
if(($this->tickCounter % self::TICKS_PER_WORLD_CACHE_CLEAR) === 0){
|
||||
foreach($this->worldManager->getWorlds() as $world){
|
||||
$world->clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
if($this->getTicksPerSecondAverage() < 12){
|
||||
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
|
||||
}
|
||||
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->getMemoryManager()->check();
|
||||
@ -1866,12 +1858,12 @@ class Server{
|
||||
|
||||
$now = microtime(true);
|
||||
$totalTickTimeSeconds = $now - $tickTime + ($this->tickSleeper->getNotificationProcessingTime() / 1_000_000_000);
|
||||
$this->currentTPS = min(20, 1 / max(0.001, $totalTickTimeSeconds));
|
||||
$this->currentUse = min(1, $totalTickTimeSeconds / 0.05);
|
||||
$this->currentTPS = min(self::TARGET_TICKS_PER_SECOND, 1 / max(0.001, $totalTickTimeSeconds));
|
||||
$this->currentUse = min(1, $totalTickTimeSeconds / self::TARGET_SECONDS_PER_TICK);
|
||||
|
||||
TimingsHandler::tick($this->currentTPS <= $this->profilingTickRate);
|
||||
|
||||
$idx = $this->tickCounter % 20;
|
||||
$idx = $this->tickCounter % self::TARGET_TICKS_PER_SECOND;
|
||||
$this->tickAverage[$idx] = $this->currentTPS;
|
||||
$this->useAverage[$idx] = $this->currentUse;
|
||||
$this->tickSleeper->resetNotificationProcessingTime();
|
||||
@ -1879,7 +1871,7 @@ class Server{
|
||||
if(($this->nextTick - $tickTime) < -1){
|
||||
$this->nextTick = $tickTime;
|
||||
}else{
|
||||
$this->nextTick += 0.05;
|
||||
$this->nextTick += self::TARGET_SECONDS_PER_TICK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.12.12";
|
||||
public const BASE_VERSION = "4.13.0-BETA2";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
|
||||
private function __construct(){
|
||||
//NOOP
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\tile\Jukebox as JukeboxTile;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\Record;
|
||||
use pocketmine\lang\KnownTranslationKeys;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\RecordSound;
|
||||
@ -44,7 +45,7 @@ class Jukebox extends Opaque{
|
||||
if($this->record !== null){
|
||||
$this->ejectRecord();
|
||||
}elseif($item instanceof Record){
|
||||
$player->sendJukeboxPopup("record.nowPlaying", [$player->getLanguage()->translate($item->getRecordType()->getTranslatableName())]);
|
||||
$player->sendJukeboxPopup(KnownTranslationKeys::RECORD_NOWPLAYING, [$player->getLanguage()->translate($item->getRecordType()->getTranslatableName())]);
|
||||
$this->insertRecord($item->pop());
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ trait ContainerTrait{
|
||||
$newContents = [];
|
||||
/** @var CompoundTag $itemNBT */
|
||||
foreach($inventoryTag as $itemNBT){
|
||||
$newContents[$itemNBT->getByte("Slot")] = Item::nbtDeserialize($itemNBT);
|
||||
$newContents[$itemNBT->getByte(Item::TAG_SLOT)] = Item::nbtDeserialize($itemNBT);
|
||||
}
|
||||
$inventory->setContents($newContents);
|
||||
|
||||
|
@ -30,7 +30,7 @@ use function array_slice;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function is_int;
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
|
||||
class SignText{
|
||||
public const LINE_COUNT = 4;
|
||||
@ -54,7 +54,7 @@ class SignText{
|
||||
foreach($lines as $k => $line){
|
||||
$this->checkLineIndex($k);
|
||||
Utils::checkUTF8($line);
|
||||
if(strpos($line, "\n") !== false){
|
||||
if(str_contains($line, "\n")){
|
||||
throw new \InvalidArgumentException("Line must not contain newlines");
|
||||
}
|
||||
//TODO: add length checks
|
||||
|
@ -72,8 +72,8 @@ use pocketmine\utils\TextFormat;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function str_contains;
|
||||
use function strcasecmp;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
@ -238,7 +238,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
$values = $this->server->getCommandAliases();
|
||||
|
||||
foreach($values as $alias => $commandStrings){
|
||||
if(strpos($alias, ":") !== false){
|
||||
if(str_contains($alias, ":")){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
|
||||
continue;
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ use pocketmine\item\LegacyStringToItemParserException;
|
||||
use pocketmine\item\StringToItemParser;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
@ -59,23 +58,9 @@ class ClearCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
if(isset($args[0])){
|
||||
$target = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
if($target === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
if($target !== $sender && !$this->testPermission($sender, DefaultPermissionNames::COMMAND_CLEAR_OTHER)){
|
||||
return true;
|
||||
}
|
||||
}elseif($sender instanceof Player){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_CLEAR_SELF)){
|
||||
return true;
|
||||
}
|
||||
|
||||
$target = $sender;
|
||||
}else{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
$targetItem = null;
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function date;
|
||||
@ -33,7 +34,7 @@ class DumpMemoryCommand extends VanillaCommand{
|
||||
public function __construct(string $name){
|
||||
parent::__construct(
|
||||
$name,
|
||||
"Dumps the memory",
|
||||
KnownTranslationFactory::pocketmine_command_dumpmemory_description(),
|
||||
"/$name [path]"
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_DUMPMEMORY);
|
||||
|
@ -32,6 +32,7 @@ use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function strtolower;
|
||||
|
||||
class EffectCommand extends VanillaCommand{
|
||||
@ -42,7 +43,10 @@ class EffectCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_effect_description(),
|
||||
KnownTranslationFactory::commands_effect_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_EFFECT);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_EFFECT_SELF,
|
||||
DefaultPermissionNames::COMMAND_EFFECT_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -54,10 +58,8 @@ class EffectCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER);
|
||||
if($player === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
$effectManager = $player->getEffects();
|
||||
|
@ -29,8 +29,8 @@ use pocketmine\item\enchantment\EnchantmentInstance;
|
||||
use pocketmine\item\enchantment\StringToEnchantmentParser;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
|
||||
class EnchantCommand extends VanillaCommand{
|
||||
|
||||
@ -40,7 +40,10 @@ class EnchantCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_enchant_description(),
|
||||
KnownTranslationFactory::commands_enchant_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_ENCHANT);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_ENCHANT_SELF,
|
||||
DefaultPermissionNames::COMMAND_ENCHANT_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -52,10 +55,8 @@ class EnchantCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER);
|
||||
if($player === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,8 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
|
||||
class GamemodeCommand extends VanillaCommand{
|
||||
|
||||
@ -41,7 +40,10 @@ class GamemodeCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_gamemode_description(),
|
||||
KnownTranslationFactory::commands_gamemode_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_GAMEMODE);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_GAMEMODE_SELF,
|
||||
DefaultPermissionNames::COMMAND_GAMEMODE_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -59,17 +61,9 @@ class GamemodeCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isset($args[1])){
|
||||
$target = $sender->getServer()->getPlayerByPrefix($args[1]);
|
||||
if($target === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
|
||||
return true;
|
||||
}
|
||||
}elseif($sender instanceof Player){
|
||||
$target = $sender;
|
||||
}else{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
if($target->getGamemode()->equals($gameMode)){
|
||||
|
@ -47,7 +47,10 @@ class GiveCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_give_description(),
|
||||
KnownTranslationFactory::pocketmine_command_give_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_GIVE);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_GIVE_SELF,
|
||||
DefaultPermissionNames::COMMAND_GIVE_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -59,9 +62,8 @@ class GiveCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER);
|
||||
if($player === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,6 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
|
||||
@ -55,32 +53,16 @@ class KillCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
if(count($args) === 1){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_KILL_OTHER)){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
|
||||
if($player instanceof Player){
|
||||
$player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_kill_successful($player->getName()));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
if($sender instanceof Player){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_KILL_SELF)){
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->attack(new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
$player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000));
|
||||
if($player === $sender){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_kill_successful($sender->getName()));
|
||||
}else{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_kill_successful($player->getName()));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -70,7 +70,7 @@ use function explode;
|
||||
use function max;
|
||||
use function microtime;
|
||||
use function mt_rand;
|
||||
use function strpos;
|
||||
use function str_starts_with;
|
||||
use function strtolower;
|
||||
|
||||
class ParticleCommand extends VanillaCommand{
|
||||
@ -208,17 +208,17 @@ class ParticleCommand extends VanillaCommand{
|
||||
return new EntityFlameParticle();
|
||||
}
|
||||
|
||||
if(strpos($name, "iconcrack_") === 0){
|
||||
if(str_starts_with($name, "iconcrack_")){
|
||||
$d = explode("_", $name);
|
||||
if(count($d) === 3){
|
||||
return new ItemBreakParticle(ItemFactory::getInstance()->get((int) $d[1], (int) $d[2]));
|
||||
}
|
||||
}elseif(strpos($name, "blockcrack_") === 0){
|
||||
}elseif(str_starts_with($name, "blockcrack_")){
|
||||
$d = explode("_", $name);
|
||||
if(count($d) === 2){
|
||||
return new TerrainParticle(BlockFactory::getInstance()->get(((int) $d[1]) & 0xff, ((int) $d[1]) >> 12));
|
||||
}
|
||||
}elseif(strpos($name, "blockdust_") === 0){
|
||||
}elseif(str_starts_with($name, "blockdust_")){
|
||||
$d = explode("_", $name);
|
||||
if(count($d) >= 4){
|
||||
return new DustParticle(new Color(((int) $d[1]) & 0xff, ((int) $d[2]) & 0xff, ((int) $d[3]) & 0xff, isset($d[4]) ? ((int) $d[4]) & 0xff : 255));
|
||||
|
@ -29,10 +29,10 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\World;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function round;
|
||||
|
||||
class SpawnpointCommand extends VanillaCommand{
|
||||
@ -43,7 +43,10 @@ class SpawnpointCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_spawnpoint_description(),
|
||||
KnownTranslationFactory::commands_spawnpoint_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_SPAWNPOINT);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF,
|
||||
DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -51,23 +54,9 @@ class SpawnpointCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
$target = null;
|
||||
|
||||
if(count($args) === 0){
|
||||
if($sender instanceof Player){
|
||||
$target = $sender;
|
||||
}else{
|
||||
$sender->sendMessage(TextFormat::RED . "Please provide a player!");
|
||||
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
$target = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
if($target === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
|
||||
return true;
|
||||
}
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(count($args) === 4){
|
||||
@ -81,19 +70,13 @@ class SpawnpointCommand extends VanillaCommand{
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($x, 2), (string) round($y, 2), (string) round($z, 2)));
|
||||
|
||||
return true;
|
||||
}elseif(count($args) <= 1){
|
||||
if($sender instanceof Player){
|
||||
$cpos = $sender->getPosition();
|
||||
$pos = Position::fromObject($cpos->floor(), $cpos->getWorld());
|
||||
$target->setSpawn($pos);
|
||||
}elseif(count($args) <= 1 && $sender instanceof Player){
|
||||
$cpos = $sender->getPosition();
|
||||
$pos = Position::fromObject($cpos->floor(), $cpos->getWorld());
|
||||
$target->setSpawn($pos);
|
||||
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($pos->x, 2), (string) round($pos->y, 2), (string) round($pos->z, 2)));
|
||||
return true;
|
||||
}else{
|
||||
$sender->sendMessage(TextFormat::RED . "Please provide a player!");
|
||||
|
||||
return true;
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($pos->x, 2), (string) round($pos->y, 2), (string) round($pos->z, 2)));
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new InvalidCommandSyntaxException();
|
||||
|
@ -35,6 +35,7 @@ use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\World;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function round;
|
||||
|
||||
class TeleportCommand extends VanillaCommand{
|
||||
@ -46,7 +47,10 @@ class TeleportCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::commands_tp_usage(),
|
||||
["teleport"]
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_TELEPORT);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_TELEPORT_SELF,
|
||||
DefaultPermissionNames::COMMAND_TELEPORT_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
private function findPlayer(CommandSender $sender, string $playerName) : ?Player{
|
||||
@ -67,31 +71,25 @@ class TeleportCommand extends VanillaCommand{
|
||||
case 1: // /tp targetPlayer
|
||||
case 3: // /tp x y z
|
||||
case 5: // /tp x y z yaw pitch - TODO: 5 args could be target x y z yaw :(
|
||||
if(!($sender instanceof Player)){
|
||||
$sender->sendMessage(TextFormat::RED . "Please provide a player!");
|
||||
return true;
|
||||
}
|
||||
|
||||
$subject = $sender;
|
||||
$targetArgs = $args;
|
||||
$subjectName = null; //self
|
||||
break;
|
||||
case 2: // /tp player1 player2
|
||||
case 4: // /tp player1 x y z - TODO: 4 args could be x y z yaw :(
|
||||
case 6: // /tp player1 x y z yaw pitch
|
||||
$subject = $this->findPlayer($sender, $args[0]);
|
||||
if($subject === null){
|
||||
return true;
|
||||
}
|
||||
$targetArgs = $args;
|
||||
array_shift($targetArgs);
|
||||
$subjectName = array_shift($args);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
switch(count($targetArgs)){
|
||||
$subject = $this->fetchPermittedPlayerTarget($sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER);
|
||||
if($subject === null){
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(count($args)){
|
||||
case 1:
|
||||
$targetPlayer = $this->findPlayer($sender, $targetArgs[0]);
|
||||
$targetPlayer = $this->findPlayer($sender, $args[0]);
|
||||
if($targetPlayer === null){
|
||||
return true;
|
||||
}
|
||||
@ -103,17 +101,17 @@ class TeleportCommand extends VanillaCommand{
|
||||
case 3:
|
||||
case 5:
|
||||
$base = $subject->getLocation();
|
||||
if(count($targetArgs) === 5){
|
||||
$yaw = (float) $targetArgs[3];
|
||||
$pitch = (float) $targetArgs[4];
|
||||
if(count($args) === 5){
|
||||
$yaw = (float) $args[3];
|
||||
$pitch = (float) $args[4];
|
||||
}else{
|
||||
$yaw = $base->yaw;
|
||||
$pitch = $base->pitch;
|
||||
}
|
||||
|
||||
$x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]);
|
||||
$y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], World::Y_MIN, World::Y_MAX);
|
||||
$z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]);
|
||||
$x = $this->getRelativeDouble($base->x, $sender, $args[0]);
|
||||
$y = $this->getRelativeDouble($base->y, $sender, $args[1], World::Y_MIN, World::Y_MAX);
|
||||
$z = $this->getRelativeDouble($base->z, $sender, $args[2]);
|
||||
$targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch);
|
||||
|
||||
$subject->teleport($targetLocation);
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function implode;
|
||||
@ -40,7 +39,10 @@ class TitleCommand extends VanillaCommand{
|
||||
KnownTranslationFactory::pocketmine_command_title_description(),
|
||||
KnownTranslationFactory::commands_title_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_TITLE);
|
||||
$this->setPermission(implode(";", [
|
||||
DefaultPermissionNames::COMMAND_TITLE_SELF,
|
||||
DefaultPermissionNames::COMMAND_TITLE_OTHER
|
||||
]));
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@ -52,9 +54,8 @@ class TitleCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getPlayerByPrefix($args[0]);
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER);
|
||||
if($player === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function is_numeric;
|
||||
use function substr;
|
||||
@ -35,6 +36,28 @@ abstract class VanillaCommand extends Command{
|
||||
public const MAX_COORD = 30000000;
|
||||
public const MIN_COORD = -30000000;
|
||||
|
||||
protected function fetchPermittedPlayerTarget(CommandSender $sender, ?string $target, string $selfPermission, string $otherPermission) : ?Player{
|
||||
if($target !== null){
|
||||
$player = $sender->getServer()->getPlayerByPrefix($target);
|
||||
}elseif($sender instanceof Player){
|
||||
$player = $sender;
|
||||
}else{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
if($player === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
if(
|
||||
($player === $sender && $this->testPermission($sender, $selfPermission)) ||
|
||||
($player !== $sender && $this->testPermission($sender, $otherPermission))
|
||||
){
|
||||
return $player;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getInteger(CommandSender $sender, string $value, int $min = self::MIN_COORD, int $max = self::MAX_COORD) : int{
|
||||
$i = (int) $value;
|
||||
|
||||
|
@ -26,9 +26,8 @@ namespace pocketmine\crafting;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use function array_map;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
|
||||
@ -52,7 +51,7 @@ final class CraftingManagerFromDataHelper{
|
||||
}
|
||||
|
||||
public static function make(string $filePath) : CraftingManager{
|
||||
$recipes = json_decode(Utils::assumeNotFalse(file_get_contents($filePath), "Missing required resource file"), true);
|
||||
$recipes = json_decode(Filesystem::fileGetContents($filePath), true);
|
||||
if(!is_array($recipes)){
|
||||
throw new AssumptionFailedError("recipes.json root should contain a map of recipe types");
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ use pocketmine\utils\Utils;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function str_contains;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
|
||||
class ShapedRecipe implements CraftingRecipe{
|
||||
/** @var string[] */
|
||||
@ -86,7 +86,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
$this->shape = $shape;
|
||||
|
||||
foreach($ingredients as $char => $i){
|
||||
if(strpos(implode($this->shape), $char) === false){
|
||||
if(!str_contains(implode($this->shape), $char)){
|
||||
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ use function phpversion;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_split;
|
||||
use function str_starts_with;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
use function zend_version;
|
||||
@ -237,7 +238,7 @@ class CrashDump{
|
||||
|
||||
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
|
||||
$frameCleanPath = Filesystem::cleanPath($filePath);
|
||||
if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){
|
||||
if(!str_starts_with($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX)){
|
||||
if($crashFrame){
|
||||
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
|
||||
}else{
|
||||
@ -250,7 +251,7 @@ class CrashDump{
|
||||
$file->setAccessible(true);
|
||||
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
|
||||
$filePath = Filesystem::cleanPath($file->getValue($plugin));
|
||||
if(strpos($frameCleanPath, $filePath) === 0){
|
||||
if(str_starts_with($frameCleanPath, $filePath)){
|
||||
$this->data->plugin = $plugin->getName();
|
||||
break;
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ final class EnchantmentIdMap{
|
||||
$this->register(EnchantmentIds::MENDING, VanillaEnchantments::MENDING());
|
||||
|
||||
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
|
||||
|
||||
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
|
||||
}
|
||||
|
||||
public function register(int $mcpeId, Enchantment $enchantment) : void{
|
||||
|
@ -66,4 +66,5 @@ final class EnchantmentIds{
|
||||
public const PIERCING = 34;
|
||||
public const QUICK_CHARGE = 35;
|
||||
public const SOUL_SPEED = 36;
|
||||
public const SWIFT_SNEAK = 37;
|
||||
}
|
||||
|
@ -24,8 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function file_get_contents;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
@ -45,7 +44,7 @@ abstract class LegacyToStringBidirectionalIdMap{
|
||||
private array $stringToLegacy = [];
|
||||
|
||||
public function __construct(string $file){
|
||||
$stringToLegacyId = json_decode(Utils::assumeNotFalse(file_get_contents($file), "Missing required resource file"), true);
|
||||
$stringToLegacyId = json_decode(Filesystem::fileGetContents($file), true);
|
||||
if(!is_array($stringToLegacyId)){
|
||||
throw new AssumptionFailedError("Invalid format of ID map");
|
||||
}
|
||||
|
@ -81,6 +81,15 @@ abstract class Entity{
|
||||
public const MOTION_THRESHOLD = 0.00001;
|
||||
protected const STEP_CLIP_MULTIPLIER = 0.4;
|
||||
|
||||
private const TAG_FIRE = "Fire"; //TAG_Short
|
||||
private const TAG_ON_GROUND = "OnGround"; //TAG_Byte
|
||||
private const TAG_FALL_DISTANCE = "FallDistance"; //TAG_Float
|
||||
private const TAG_CUSTOM_NAME = "CustomName"; //TAG_String
|
||||
private const TAG_CUSTOM_NAME_VISIBLE = "CustomNameVisible"; //TAG_Byte
|
||||
public const TAG_POS = "Pos"; //TAG_List<TAG_Double>|TAG_List<TAG_Float>
|
||||
public const TAG_MOTION = "Motion"; //TAG_List<TAG_Double>|TAG_List<TAG_Float>
|
||||
public const TAG_ROTATION = "Rotation"; //TAG_List<TAG_Float>
|
||||
|
||||
private static int $entityCount = 1;
|
||||
|
||||
/**
|
||||
@ -233,7 +242,7 @@ abstract class Entity{
|
||||
$this->recalculateBoundingBox();
|
||||
|
||||
if($nbt !== null){
|
||||
$this->motion = EntityDataHelper::parseVec3($nbt, "Motion", true);
|
||||
$this->motion = EntityDataHelper::parseVec3($nbt, self::TAG_MOTION, true);
|
||||
}else{
|
||||
$this->motion = new Vector3(0, 0, 0);
|
||||
}
|
||||
@ -466,17 +475,17 @@ abstract class Entity{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = CompoundTag::create()
|
||||
->setTag("Pos", new ListTag([
|
||||
->setTag(self::TAG_POS, new ListTag([
|
||||
new DoubleTag($this->location->x),
|
||||
new DoubleTag($this->location->y),
|
||||
new DoubleTag($this->location->z)
|
||||
]))
|
||||
->setTag("Motion", new ListTag([
|
||||
->setTag(self::TAG_MOTION, new ListTag([
|
||||
new DoubleTag($this->motion->x),
|
||||
new DoubleTag($this->motion->y),
|
||||
new DoubleTag($this->motion->z)
|
||||
]))
|
||||
->setTag("Rotation", new ListTag([
|
||||
->setTag(self::TAG_ROTATION, new ListTag([
|
||||
new FloatTag($this->location->yaw),
|
||||
new FloatTag($this->location->pitch)
|
||||
]));
|
||||
@ -485,33 +494,33 @@ abstract class Entity{
|
||||
EntityFactory::getInstance()->injectSaveId(get_class($this), $nbt);
|
||||
|
||||
if($this->getNameTag() !== ""){
|
||||
$nbt->setString("CustomName", $this->getNameTag());
|
||||
$nbt->setByte("CustomNameVisible", $this->isNameTagVisible() ? 1 : 0);
|
||||
$nbt->setString(self::TAG_CUSTOM_NAME, $this->getNameTag());
|
||||
$nbt->setByte(self::TAG_CUSTOM_NAME_VISIBLE, $this->isNameTagVisible() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
$nbt->setFloat("FallDistance", $this->fallDistance);
|
||||
$nbt->setShort("Fire", $this->fireTicks);
|
||||
$nbt->setByte("OnGround", $this->onGround ? 1 : 0);
|
||||
$nbt->setFloat(self::TAG_FALL_DISTANCE, $this->fallDistance);
|
||||
$nbt->setShort(self::TAG_FIRE, $this->fireTicks);
|
||||
$nbt->setByte(self::TAG_ON_GROUND, $this->onGround ? 1 : 0);
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
protected function initEntity(CompoundTag $nbt) : void{
|
||||
$this->fireTicks = $nbt->getShort("Fire", 0);
|
||||
$this->fireTicks = $nbt->getShort(self::TAG_FIRE, 0);
|
||||
|
||||
$this->onGround = $nbt->getByte("OnGround", 0) !== 0;
|
||||
$this->onGround = $nbt->getByte(self::TAG_ON_GROUND, 0) !== 0;
|
||||
|
||||
$this->fallDistance = $nbt->getFloat("FallDistance", 0.0);
|
||||
$this->fallDistance = $nbt->getFloat(self::TAG_FALL_DISTANCE, 0.0);
|
||||
|
||||
if(($customNameTag = $nbt->getTag("CustomName")) instanceof StringTag){
|
||||
if(($customNameTag = $nbt->getTag(self::TAG_CUSTOM_NAME)) instanceof StringTag){
|
||||
$this->setNameTag($customNameTag->getValue());
|
||||
|
||||
if(($customNameVisibleTag = $nbt->getTag("CustomNameVisible")) instanceof StringTag){
|
||||
if(($customNameVisibleTag = $nbt->getTag(self::TAG_CUSTOM_NAME_VISIBLE)) instanceof StringTag){
|
||||
//Older versions incorrectly saved this as a string (see 890f72dbf23a77f294169b79590770470041adc4)
|
||||
$this->setNameTagVisible($customNameVisibleTag->getValue() !== "");
|
||||
}else{
|
||||
$this->setNameTagVisible($nbt->getByte("CustomNameVisible", 1) !== 0);
|
||||
$this->setNameTagVisible($nbt->getByte(self::TAG_CUSTOM_NAME_VISIBLE, 1) !== 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,19 +57,19 @@ final class EntityDataHelper{
|
||||
* @throws SavedDataLoadingException
|
||||
*/
|
||||
public static function parseLocation(CompoundTag $nbt, World $world) : Location{
|
||||
$pos = self::parseVec3($nbt, "Pos", false);
|
||||
$pos = self::parseVec3($nbt, Entity::TAG_POS, false);
|
||||
|
||||
$yawPitch = $nbt->getTag("Rotation");
|
||||
$yawPitch = $nbt->getTag(Entity::TAG_ROTATION);
|
||||
if(!($yawPitch instanceof ListTag) || $yawPitch->getTagType() !== NBT::TAG_Float){
|
||||
throw new SavedDataLoadingException("'Rotation' should be a List<Float>");
|
||||
throw new SavedDataLoadingException("'" . Entity::TAG_ROTATION . "' should be a List<Float>");
|
||||
}
|
||||
/** @var FloatTag[] $values */
|
||||
$values = $yawPitch->getValue();
|
||||
if(count($values) !== 2){
|
||||
throw new SavedDataLoadingException("Expected exactly 2 entries for 'Rotation'");
|
||||
}
|
||||
self::validateFloat("Rotation", "yaw", $values[0]->getValue());
|
||||
self::validateFloat("Rotation", "pitch", $values[1]->getValue());
|
||||
self::validateFloat(Entity::TAG_ROTATION, "yaw", $values[0]->getValue());
|
||||
self::validateFloat(Entity::TAG_ROTATION, "pitch", $values[1]->getValue());
|
||||
|
||||
return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue());
|
||||
}
|
||||
|
@ -66,6 +66,9 @@ use function reset;
|
||||
final class EntityFactory{
|
||||
use SingletonTrait;
|
||||
|
||||
public const TAG_IDENTIFIER = "identifier"; //TAG_String
|
||||
public const TAG_LEGACY_ID = "id"; //TAG_Int
|
||||
|
||||
/**
|
||||
* @var \Closure[] save ID => creator function
|
||||
* @phpstan-var array<int|string, \Closure(World, CompoundTag) : Entity>
|
||||
@ -113,9 +116,9 @@ final class EntityFactory{
|
||||
}, ['FallingSand', 'minecraft:falling_block'], LegacyIds::FALLING_BLOCK);
|
||||
|
||||
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
|
||||
$itemTag = $nbt->getCompoundTag("Item");
|
||||
$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
|
||||
if($itemTag === null){
|
||||
throw new SavedDataLoadingException("Expected \"Item\" NBT tag not found");
|
||||
throw new SavedDataLoadingException("Expected \"" . ItemEntity::TAG_ITEM . "\" NBT tag not found");
|
||||
}
|
||||
|
||||
$item = Item::nbtDeserialize($itemTag);
|
||||
@ -126,14 +129,14 @@ final class EntityFactory{
|
||||
}, ['Item', 'minecraft:item'], LegacyIds::ITEM);
|
||||
|
||||
$this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{
|
||||
$motive = PaintingMotive::getMotiveByName($nbt->getString("Motive"));
|
||||
$motive = PaintingMotive::getMotiveByName($nbt->getString(Painting::TAG_MOTIVE));
|
||||
if($motive === null){
|
||||
throw new SavedDataLoadingException("Unknown painting motive");
|
||||
}
|
||||
$blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ"));
|
||||
if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){
|
||||
$blockIn = new Vector3($nbt->getInt(Painting::TAG_TILE_X), $nbt->getInt(Painting::TAG_TILE_Y), $nbt->getInt(Painting::TAG_TILE_Z));
|
||||
if(($directionTag = $nbt->getTag(Painting::TAG_DIRECTION_BE)) instanceof ByteTag){
|
||||
$facing = Painting::DATA_TO_FACING[$directionTag->getValue()] ?? Facing::NORTH;
|
||||
}elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){
|
||||
}elseif(($facingTag = $nbt->getTag(Painting::TAG_FACING_JE)) instanceof ByteTag){
|
||||
$facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH;
|
||||
}else{
|
||||
throw new SavedDataLoadingException("Missing facing info");
|
||||
@ -151,7 +154,7 @@ final class EntityFactory{
|
||||
}, ['Snowball', 'minecraft:snowball'], LegacyIds::SNOWBALL);
|
||||
|
||||
$this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{
|
||||
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER));
|
||||
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort(SplashPotion::TAG_POTION_ID, PotionTypeIds::WATER));
|
||||
if($potionType === null){
|
||||
throw new SavedDataLoadingException("No such potion type");
|
||||
}
|
||||
@ -217,7 +220,7 @@ final class EntityFactory{
|
||||
*/
|
||||
public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
|
||||
try{
|
||||
$saveId = $nbt->getTag("identifier") ?? $nbt->getTag("id");
|
||||
$saveId = $nbt->getTag(self::TAG_IDENTIFIER) ?? $nbt->getTag(self::TAG_LEGACY_ID);
|
||||
$func = null;
|
||||
if($saveId instanceof StringTag){
|
||||
$func = $this->creationFuncs[$saveId->getValue()] ?? null;
|
||||
@ -238,7 +241,7 @@ final class EntityFactory{
|
||||
|
||||
public function injectSaveId(string $class, CompoundTag $saveData) : void{
|
||||
if(isset($this->saveNames[$class])){
|
||||
$saveData->setTag("identifier", new StringTag($this->saveNames[$class]));
|
||||
$saveData->setTag(self::TAG_IDENTIFIER, new StringTag($this->saveNames[$class]));
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Entity $class is not registered");
|
||||
}
|
||||
|
@ -77,6 +77,26 @@ use function random_int;
|
||||
|
||||
class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
|
||||
private const TAG_INVENTORY = "Inventory"; //TAG_List<TAG_Compound>
|
||||
private const TAG_OFF_HAND_ITEM = "OffHandItem"; //TAG_Compound
|
||||
private const TAG_ENDER_CHEST_INVENTORY = "EnderChestInventory"; //TAG_List<TAG_Compound>
|
||||
private const TAG_SELECTED_INVENTORY_SLOT = "SelectedInventorySlot"; //TAG_Int
|
||||
private const TAG_FOOD_LEVEL = "foodLevel"; //TAG_Int
|
||||
private const TAG_FOOD_EXHAUSTION_LEVEL = "foodExhaustionLevel"; //TAG_Float
|
||||
private const TAG_FOOD_SATURATION_LEVEL = "foodSaturationLevel"; //TAG_Float
|
||||
private const TAG_FOOD_TICK_TIMER = "foodTickTimer"; //TAG_Int
|
||||
private const TAG_XP_LEVEL = "XpLevel"; //TAG_Int
|
||||
private const TAG_XP_PROGRESS = "XpP"; //TAG_Float
|
||||
private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int
|
||||
private const TAG_XP_SEED = "XpSeed"; //TAG_Int
|
||||
private const TAG_NAME_TAG = "NameTag"; //TAG_String
|
||||
private const TAG_SKIN = "Skin"; //TAG_Compound
|
||||
private const TAG_SKIN_NAME = "Name"; //TAG_String
|
||||
private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray
|
||||
private const TAG_SKIN_CAPE_DATA = "CapeData"; //TAG_ByteArray
|
||||
private const TAG_SKIN_GEOMETRY_NAME = "GeometryName"; //TAG_String
|
||||
private const TAG_SKIN_GEOMETRY_DATA = "GeometryData"; //TAG_ByteArray
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::PLAYER; }
|
||||
|
||||
/** @var PlayerInventory */
|
||||
@ -114,16 +134,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
* @throws SavedDataLoadingException
|
||||
*/
|
||||
public static function parseSkinNBT(CompoundTag $nbt) : Skin{
|
||||
$skinTag = $nbt->getCompoundTag("Skin");
|
||||
$skinTag = $nbt->getCompoundTag(self::TAG_SKIN);
|
||||
if($skinTag === null){
|
||||
throw new SavedDataLoadingException("Missing skin data");
|
||||
}
|
||||
return new Skin( //this throws if the skin is invalid
|
||||
$skinTag->getString("Name"),
|
||||
($skinDataTag = $skinTag->getTag("Data")) instanceof StringTag ? $skinDataTag->getValue() : $skinTag->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
|
||||
$skinTag->getByteArray("CapeData", ""),
|
||||
$skinTag->getString("GeometryName", ""),
|
||||
$skinTag->getByteArray("GeometryData", "")
|
||||
$skinTag->getString(self::TAG_SKIN_NAME),
|
||||
($skinDataTag = $skinTag->getTag(self::TAG_SKIN_DATA)) instanceof StringTag ? $skinDataTag->getValue() : $skinTag->getByteArray(self::TAG_SKIN_DATA), //old data (this used to be saved as a StringTag in older versions of PM)
|
||||
$skinTag->getByteArray(self::TAG_SKIN_CAPE_DATA, ""),
|
||||
$skinTag->getString(self::TAG_SKIN_GEOMETRY_NAME, ""),
|
||||
$skinTag->getByteArray(self::TAG_SKIN_GEOMETRY_DATA, "")
|
||||
);
|
||||
}
|
||||
|
||||
@ -221,7 +241,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
|
||||
*/
|
||||
protected function initHumanData(CompoundTag $nbt) : void{
|
||||
if(($nameTagTag = $nbt->getTag("NameTag")) instanceof StringTag){
|
||||
if(($nameTagTag = $nbt->getTag(self::TAG_NAME_TAG)) instanceof StringTag){
|
||||
$this->setNameTag($nameTagTag->getValue());
|
||||
}
|
||||
|
||||
@ -270,14 +290,14 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
$this->enderInventory = new PlayerEnderInventory($this);
|
||||
$this->initHumanData($nbt);
|
||||
|
||||
$inventoryTag = $nbt->getListTag("Inventory");
|
||||
$inventoryTag = $nbt->getListTag(self::TAG_INVENTORY);
|
||||
if($inventoryTag !== null){
|
||||
$inventoryItems = [];
|
||||
$armorInventoryItems = [];
|
||||
|
||||
/** @var CompoundTag $item */
|
||||
foreach($inventoryTag as $i => $item){
|
||||
$slot = $item->getByte("Slot");
|
||||
$slot = $item->getByte(Item::TAG_SLOT);
|
||||
if($slot >= 0 && $slot < 9){ //Hotbar
|
||||
//Old hotbar saving stuff, ignore it
|
||||
}elseif($slot >= 100 && $slot < 104){ //Armor
|
||||
@ -290,7 +310,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
self::populateInventoryFromListTag($this->inventory, $inventoryItems);
|
||||
self::populateInventoryFromListTag($this->armorInventory, $armorInventoryItems);
|
||||
}
|
||||
$offHand = $nbt->getCompoundTag("OffHandItem");
|
||||
$offHand = $nbt->getCompoundTag(self::TAG_OFF_HAND_ITEM);
|
||||
if($offHand !== null){
|
||||
$this->offHandInventory->setItem(0, Item::nbtDeserialize($offHand));
|
||||
}
|
||||
@ -300,35 +320,35 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
}));
|
||||
|
||||
$enderChestInventoryTag = $nbt->getListTag("EnderChestInventory");
|
||||
$enderChestInventoryTag = $nbt->getListTag(self::TAG_ENDER_CHEST_INVENTORY);
|
||||
if($enderChestInventoryTag !== null){
|
||||
$enderChestInventoryItems = [];
|
||||
|
||||
/** @var CompoundTag $item */
|
||||
foreach($enderChestInventoryTag as $i => $item){
|
||||
$enderChestInventoryItems[$item->getByte("Slot")] = Item::nbtDeserialize($item);
|
||||
$enderChestInventoryItems[$item->getByte(Item::TAG_SLOT)] = Item::nbtDeserialize($item);
|
||||
}
|
||||
self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
|
||||
}
|
||||
|
||||
$this->inventory->setHeldItemIndex($nbt->getInt("SelectedInventorySlot", 0));
|
||||
$this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0));
|
||||
$this->inventory->getHeldItemIndexChangeListeners()->add(function(int $oldIndex) : void{
|
||||
foreach($this->getViewers() as $viewer){
|
||||
$viewer->getNetworkSession()->onMobMainHandItemChange($this);
|
||||
}
|
||||
});
|
||||
|
||||
$this->hungerManager->setFood((float) $nbt->getInt("foodLevel", (int) $this->hungerManager->getFood()));
|
||||
$this->hungerManager->setExhaustion($nbt->getFloat("foodExhaustionLevel", $this->hungerManager->getExhaustion()));
|
||||
$this->hungerManager->setSaturation($nbt->getFloat("foodSaturationLevel", $this->hungerManager->getSaturation()));
|
||||
$this->hungerManager->setFoodTickTimer($nbt->getInt("foodTickTimer", $this->hungerManager->getFoodTickTimer()));
|
||||
$this->hungerManager->setFood((float) $nbt->getInt(self::TAG_FOOD_LEVEL, (int) $this->hungerManager->getFood()));
|
||||
$this->hungerManager->setExhaustion($nbt->getFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion()));
|
||||
$this->hungerManager->setSaturation($nbt->getFloat(self::TAG_FOOD_SATURATION_LEVEL, $this->hungerManager->getSaturation()));
|
||||
$this->hungerManager->setFoodTickTimer($nbt->getInt(self::TAG_FOOD_TICK_TIMER, $this->hungerManager->getFoodTickTimer()));
|
||||
|
||||
$this->xpManager->setXpAndProgressNoEvent(
|
||||
$nbt->getInt("XpLevel", 0),
|
||||
$nbt->getFloat("XpP", 0.0));
|
||||
$this->xpManager->setLifetimeTotalXp($nbt->getInt("XpTotal", 0));
|
||||
$nbt->getInt(self::TAG_XP_LEVEL, 0),
|
||||
$nbt->getFloat(self::TAG_XP_PROGRESS, 0.0));
|
||||
$this->xpManager->setLifetimeTotalXp($nbt->getInt(self::TAG_LIFETIME_XP_TOTAL, 0));
|
||||
|
||||
if(($xpSeedTag = $nbt->getTag("XpSeed")) instanceof IntTag){
|
||||
if(($xpSeedTag = $nbt->getTag(self::TAG_XP_SEED)) instanceof IntTag){
|
||||
$this->xpSeed = $xpSeedTag->getValue();
|
||||
}else{
|
||||
$this->xpSeed = random_int(Limits::INT32_MIN, Limits::INT32_MAX);
|
||||
@ -391,24 +411,24 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
$this->inventory !== null ? array_values($this->inventory->getContents()) : [],
|
||||
$this->armorInventory !== null ? array_values($this->armorInventory->getContents()) : [],
|
||||
$this->offHandInventory !== null ? array_values($this->offHandInventory->getContents()) : [],
|
||||
), function(Item $item) : bool{ return !$item->hasEnchantment(VanillaEnchantments::VANISHING()); });
|
||||
), function(Item $item) : bool{ return !$item->hasEnchantment(VanillaEnchantments::VANISHING()) && !$item->keepOnDeath(); });
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setInt("foodLevel", (int) $this->hungerManager->getFood());
|
||||
$nbt->setFloat("foodExhaustionLevel", $this->hungerManager->getExhaustion());
|
||||
$nbt->setFloat("foodSaturationLevel", $this->hungerManager->getSaturation());
|
||||
$nbt->setInt("foodTickTimer", $this->hungerManager->getFoodTickTimer());
|
||||
$nbt->setInt(self::TAG_FOOD_LEVEL, (int) $this->hungerManager->getFood());
|
||||
$nbt->setFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion());
|
||||
$nbt->setFloat(self::TAG_FOOD_SATURATION_LEVEL, $this->hungerManager->getSaturation());
|
||||
$nbt->setInt(self::TAG_FOOD_TICK_TIMER, $this->hungerManager->getFoodTickTimer());
|
||||
|
||||
$nbt->setInt("XpLevel", $this->xpManager->getXpLevel());
|
||||
$nbt->setFloat("XpP", $this->xpManager->getXpProgress());
|
||||
$nbt->setInt("XpTotal", $this->xpManager->getLifetimeTotalXp());
|
||||
$nbt->setInt("XpSeed", $this->xpSeed);
|
||||
$nbt->setInt(self::TAG_XP_LEVEL, $this->xpManager->getXpLevel());
|
||||
$nbt->setFloat(self::TAG_XP_PROGRESS, $this->xpManager->getXpProgress());
|
||||
$nbt->setInt(self::TAG_LIFETIME_XP_TOTAL, $this->xpManager->getLifetimeTotalXp());
|
||||
$nbt->setInt(self::TAG_XP_SEED, $this->xpSeed);
|
||||
|
||||
$inventoryTag = new ListTag([], NBT::TAG_Compound);
|
||||
$nbt->setTag("Inventory", $inventoryTag);
|
||||
$nbt->setTag(self::TAG_INVENTORY, $inventoryTag);
|
||||
if($this->inventory !== null){
|
||||
//Normal inventory
|
||||
$slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize();
|
||||
@ -427,11 +447,11 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
}
|
||||
|
||||
$nbt->setInt("SelectedInventorySlot", $this->inventory->getHeldItemIndex());
|
||||
$nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->inventory->getHeldItemIndex());
|
||||
}
|
||||
$offHandItem = $this->offHandInventory->getItem(0);
|
||||
if(!$offHandItem->isNull()){
|
||||
$nbt->setTag("OffHandItem", $offHandItem->nbtSerialize());
|
||||
$nbt->setTag(self::TAG_OFF_HAND_ITEM, $offHandItem->nbtSerialize());
|
||||
}
|
||||
|
||||
if($this->enderInventory !== null){
|
||||
@ -446,16 +466,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
}
|
||||
|
||||
$nbt->setTag("EnderChestInventory", new ListTag($items, NBT::TAG_Compound));
|
||||
$nbt->setTag(self::TAG_ENDER_CHEST_INVENTORY, new ListTag($items, NBT::TAG_Compound));
|
||||
}
|
||||
|
||||
if($this->skin !== null){
|
||||
$nbt->setTag("Skin", CompoundTag::create()
|
||||
->setString("Name", $this->skin->getSkinId())
|
||||
->setByteArray("Data", $this->skin->getSkinData())
|
||||
->setByteArray("CapeData", $this->skin->getCapeData())
|
||||
->setString("GeometryName", $this->skin->getGeometryName())
|
||||
->setByteArray("GeometryData", $this->skin->getGeometryData())
|
||||
$nbt->setTag(self::TAG_SKIN, CompoundTag::create()
|
||||
->setString(self::TAG_SKIN_NAME, $this->skin->getSkinId())
|
||||
->setByteArray(self::TAG_SKIN_DATA, $this->skin->getSkinData())
|
||||
->setByteArray(self::TAG_SKIN_CAPE_DATA, $this->skin->getCapeData())
|
||||
->setString(self::TAG_SKIN_GEOMETRY_NAME, $this->skin->getGeometryName())
|
||||
->setByteArray(self::TAG_SKIN_GEOMETRY_DATA, $this->skin->getGeometryData())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,16 @@ use const M_PI;
|
||||
abstract class Living extends Entity{
|
||||
protected const DEFAULT_BREATH_TICKS = 300;
|
||||
|
||||
private const TAG_LEGACY_HEALTH = "HealF"; //TAG_Float
|
||||
private const TAG_HEALTH = "Health"; //TAG_Float
|
||||
private const TAG_BREATH_TICKS = "Air"; //TAG_Short
|
||||
private const TAG_ACTIVE_EFFECTS = "ActiveEffects"; //TAG_List<TAG_Compound>
|
||||
private const TAG_EFFECT_ID = "Id"; //TAG_Byte
|
||||
private const TAG_EFFECT_DURATION = "Duration"; //TAG_Int
|
||||
private const TAG_EFFECT_AMPLIFIER = "Amplifier"; //TAG_Byte
|
||||
private const TAG_EFFECT_SHOW_PARTICLES = "ShowParticles"; //TAG_Byte
|
||||
private const TAG_EFFECT_AMBIENT = "Ambient"; //TAG_Byte
|
||||
|
||||
protected $gravity = 0.08;
|
||||
protected $drag = 0.02;
|
||||
|
||||
@ -143,9 +153,9 @@ abstract class Living extends Entity{
|
||||
|
||||
$health = $this->getMaxHealth();
|
||||
|
||||
if(($healFTag = $nbt->getTag("HealF")) instanceof FloatTag){
|
||||
if(($healFTag = $nbt->getTag(self::TAG_LEGACY_HEALTH)) instanceof FloatTag){
|
||||
$health = $healFTag->getValue();
|
||||
}elseif(($healthTag = $nbt->getTag("Health")) instanceof ShortTag){
|
||||
}elseif(($healthTag = $nbt->getTag(self::TAG_HEALTH)) instanceof ShortTag){
|
||||
$health = $healthTag->getValue(); //Older versions of PocketMine-MP incorrectly saved this as a short instead of a float
|
||||
}elseif($healthTag instanceof FloatTag){
|
||||
$health = $healthTag->getValue();
|
||||
@ -153,23 +163,23 @@ abstract class Living extends Entity{
|
||||
|
||||
$this->setHealth($health);
|
||||
|
||||
$this->setAirSupplyTicks($nbt->getShort("Air", self::DEFAULT_BREATH_TICKS));
|
||||
$this->setAirSupplyTicks($nbt->getShort(self::TAG_BREATH_TICKS, self::DEFAULT_BREATH_TICKS));
|
||||
|
||||
/** @var CompoundTag[]|ListTag|null $activeEffectsTag */
|
||||
$activeEffectsTag = $nbt->getListTag("ActiveEffects");
|
||||
$activeEffectsTag = $nbt->getListTag(self::TAG_ACTIVE_EFFECTS);
|
||||
if($activeEffectsTag !== null){
|
||||
foreach($activeEffectsTag as $e){
|
||||
$effect = EffectIdMap::getInstance()->fromId($e->getByte("Id"));
|
||||
$effect = EffectIdMap::getInstance()->fromId($e->getByte(self::TAG_EFFECT_ID));
|
||||
if($effect === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->effectManager->add(new EffectInstance(
|
||||
$effect,
|
||||
$e->getInt("Duration"),
|
||||
Binary::unsignByte($e->getByte("Amplifier")),
|
||||
$e->getByte("ShowParticles", 1) !== 0,
|
||||
$e->getByte("Ambient", 0) !== 0
|
||||
$e->getInt(self::TAG_EFFECT_DURATION),
|
||||
Binary::unsignByte($e->getByte(self::TAG_EFFECT_AMPLIFIER)),
|
||||
$e->getByte(self::TAG_EFFECT_SHOW_PARTICLES, 1) !== 0,
|
||||
$e->getByte(self::TAG_EFFECT_AMBIENT, 0) !== 0
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -184,6 +194,13 @@ abstract class Living extends Entity{
|
||||
$this->attributeMap->add($this->absorptionAttr = AttributeFactory::getInstance()->mustGet(Attribute::ABSORPTION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name used to describe this entity in chat and command outputs.
|
||||
*/
|
||||
public function getDisplayName() : string{
|
||||
return $this->nameTag !== "" ? $this->nameTag : $this->getName();
|
||||
}
|
||||
|
||||
public function setHealth(float $amount) : void{
|
||||
$wasAlive = $this->isAlive();
|
||||
parent::setHealth($amount);
|
||||
@ -272,22 +289,22 @@ abstract class Living extends Entity{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setFloat("Health", $this->getHealth());
|
||||
$nbt->setFloat(self::TAG_HEALTH, $this->getHealth());
|
||||
|
||||
$nbt->setShort("Air", $this->getAirSupplyTicks());
|
||||
$nbt->setShort(self::TAG_BREATH_TICKS, $this->getAirSupplyTicks());
|
||||
|
||||
if(count($this->effectManager->all()) > 0){
|
||||
$effects = [];
|
||||
foreach($this->effectManager->all() as $effect){
|
||||
$effects[] = CompoundTag::create()
|
||||
->setByte("Id", EffectIdMap::getInstance()->toId($effect->getType()))
|
||||
->setByte("Amplifier", Binary::signByte($effect->getAmplifier()))
|
||||
->setInt("Duration", $effect->getDuration())
|
||||
->setByte("Ambient", $effect->isAmbient() ? 1 : 0)
|
||||
->setByte("ShowParticles", $effect->isVisible() ? 1 : 0);
|
||||
->setByte(self::TAG_EFFECT_ID, EffectIdMap::getInstance()->toId($effect->getType()))
|
||||
->setByte(self::TAG_EFFECT_AMPLIFIER, Binary::signByte($effect->getAmplifier()))
|
||||
->setInt(self::TAG_EFFECT_DURATION, $effect->getDuration())
|
||||
->setByte(self::TAG_EFFECT_AMBIENT, $effect->isAmbient() ? 1 : 0)
|
||||
->setByte(self::TAG_EFFECT_SHOW_PARTICLES, $effect->isVisible() ? 1 : 0);
|
||||
}
|
||||
|
||||
$nbt->setTag("ActiveEffects", new ListTag($effects));
|
||||
$nbt->setTag(self::TAG_ACTIVE_EFFECTS, new ListTag($effects));
|
||||
}
|
||||
|
||||
return $nbt;
|
||||
@ -448,7 +465,9 @@ abstract class Living extends Entity{
|
||||
*/
|
||||
protected function applyPostDamageEffects(EntityDamageEvent $source) : void{
|
||||
$this->setAbsorption(max(0, $this->getAbsorption() + $source->getModifier(EntityDamageEvent::MODIFIER_ABSORPTION)));
|
||||
$this->damageArmor($source->getBaseDamage());
|
||||
if($source->canBeReducedByArmor()){
|
||||
$this->damageArmor($source->getBaseDamage());
|
||||
}
|
||||
|
||||
if($source instanceof EntityDamageByEntityEvent && ($attacker = $source->getDamager()) !== null){
|
||||
$damage = 0;
|
||||
|
@ -36,6 +36,8 @@ class Villager extends Living implements Ageable{
|
||||
public const PROFESSION_BLACKSMITH = 3;
|
||||
public const PROFESSION_BUTCHER = 4;
|
||||
|
||||
private const TAG_PROFESSION = "Profession"; //TAG_Int
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::VILLAGER; }
|
||||
|
||||
private bool $baby = false;
|
||||
@ -53,7 +55,7 @@ class Villager extends Living implements Ageable{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
/** @var int $profession */
|
||||
$profession = $nbt->getInt("Profession", self::PROFESSION_FARMER);
|
||||
$profession = $nbt->getInt(self::TAG_PROFESSION, self::PROFESSION_FARMER);
|
||||
|
||||
if($profession > 4 || $profession < 0){
|
||||
$profession = self::PROFESSION_FARMER;
|
||||
@ -64,7 +66,7 @@ class Villager extends Living implements Ageable{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setInt("Profession", $this->getProfession());
|
||||
$nbt->setInt(self::TAG_PROFESSION, $this->getProfession());
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ class ExperienceOrb extends Entity{
|
||||
|
||||
public const TAG_VALUE_PC = "Value"; //short
|
||||
public const TAG_VALUE_PE = "experience value"; //int (WTF?)
|
||||
private const TAG_AGE = "Age"; //TAG_Short
|
||||
|
||||
/** Max distance an orb will follow a player across. */
|
||||
public const MAX_TARGET_DISTANCE = 8.0;
|
||||
@ -109,13 +110,13 @@ class ExperienceOrb extends Entity{
|
||||
protected function initEntity(CompoundTag $nbt) : void{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->age = $nbt->getShort("Age", 0);
|
||||
$this->age = $nbt->getShort(self::TAG_AGE, 0);
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setShort("Age", $this->age);
|
||||
$nbt->setShort(self::TAG_AGE, $this->age);
|
||||
|
||||
$nbt->setShort(self::TAG_VALUE_PC, $this->getXpValue());
|
||||
$nbt->setInt(self::TAG_VALUE_PE, $this->getXpValue());
|
||||
|
@ -44,6 +44,10 @@ use function abs;
|
||||
|
||||
class FallingBlock extends Entity{
|
||||
|
||||
private const TAG_TILE_ID = "TileID"; //TAG_Int
|
||||
private const TAG_TILE = "Tile"; //TAG_Byte
|
||||
private const TAG_DATA = "Data"; //TAG_Byte
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::FALLING_BLOCK; }
|
||||
|
||||
protected $gravity = 0.04;
|
||||
@ -65,9 +69,9 @@ class FallingBlock extends Entity{
|
||||
$blockId = 0;
|
||||
|
||||
//TODO: 1.8+ save format
|
||||
if(($tileIdTag = $nbt->getTag("TileID")) instanceof IntTag){
|
||||
if(($tileIdTag = $nbt->getTag(self::TAG_TILE_ID)) instanceof IntTag){
|
||||
$blockId = $tileIdTag->getValue();
|
||||
}elseif(($tileTag = $nbt->getTag("Tile")) instanceof ByteTag){
|
||||
}elseif(($tileTag = $nbt->getTag(self::TAG_TILE)) instanceof ByteTag){
|
||||
$blockId = $tileTag->getValue();
|
||||
}
|
||||
|
||||
@ -75,7 +79,7 @@ class FallingBlock extends Entity{
|
||||
throw new SavedDataLoadingException("Missing block info from NBT");
|
||||
}
|
||||
|
||||
$damage = $nbt->getByte("Data", 0);
|
||||
$damage = $nbt->getByte(self::TAG_DATA, 0);
|
||||
|
||||
return $factory->get($blockId, $damage);
|
||||
}
|
||||
@ -138,8 +142,8 @@ class FallingBlock extends Entity{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setInt("TileID", $this->block->getId());
|
||||
$nbt->setByte("Data", $this->block->getMeta());
|
||||
$nbt->setInt(self::TAG_TILE_ID, $this->block->getId());
|
||||
$nbt->setByte(self::TAG_DATA, $this->block->getMeta());
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
@ -43,6 +43,13 @@ use function max;
|
||||
|
||||
class ItemEntity extends Entity{
|
||||
|
||||
private const TAG_HEALTH = "Health"; //TAG_Short
|
||||
private const TAG_AGE = "Age"; //TAG_Short
|
||||
private const TAG_PICKUP_DELAY = "PickupDelay"; //TAG_Short
|
||||
private const TAG_OWNER = "Owner"; //TAG_String
|
||||
private const TAG_THROWER = "Thrower"; //TAG_String
|
||||
public const TAG_ITEM = "Item"; //TAG_Compound
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::ITEM; }
|
||||
|
||||
public const MERGE_CHECK_PERIOD = 2; //0.1 seconds
|
||||
@ -81,17 +88,17 @@ class ItemEntity extends Entity{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->setMaxHealth(5);
|
||||
$this->setHealth($nbt->getShort("Health", (int) $this->getHealth()));
|
||||
$this->setHealth($nbt->getShort(self::TAG_HEALTH, (int) $this->getHealth()));
|
||||
|
||||
$age = $nbt->getShort("Age", 0);
|
||||
$age = $nbt->getShort(self::TAG_AGE, 0);
|
||||
if($age === -32768){
|
||||
$this->despawnDelay = self::NEVER_DESPAWN;
|
||||
}else{
|
||||
$this->despawnDelay = max(0, self::DEFAULT_DESPAWN_DELAY - $age);
|
||||
}
|
||||
$this->pickupDelay = $nbt->getShort("PickupDelay", $this->pickupDelay);
|
||||
$this->owner = $nbt->getString("Owner", $this->owner);
|
||||
$this->thrower = $nbt->getString("Thrower", $this->thrower);
|
||||
$this->pickupDelay = $nbt->getShort(self::TAG_PICKUP_DELAY, $this->pickupDelay);
|
||||
$this->owner = $nbt->getString(self::TAG_OWNER, $this->owner);
|
||||
$this->thrower = $nbt->getString(self::TAG_THROWER, $this->thrower);
|
||||
}
|
||||
|
||||
protected function onFirstUpdate(int $currentTick) : void{
|
||||
@ -195,20 +202,20 @@ class ItemEntity extends Entity{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setTag("Item", $this->item->nbtSerialize());
|
||||
$nbt->setShort("Health", (int) $this->getHealth());
|
||||
$nbt->setTag(self::TAG_ITEM, $this->item->nbtSerialize());
|
||||
$nbt->setShort(self::TAG_HEALTH, (int) $this->getHealth());
|
||||
if($this->despawnDelay === self::NEVER_DESPAWN){
|
||||
$age = -32768;
|
||||
}else{
|
||||
$age = self::DEFAULT_DESPAWN_DELAY - $this->despawnDelay;
|
||||
}
|
||||
$nbt->setShort("Age", $age);
|
||||
$nbt->setShort("PickupDelay", $this->pickupDelay);
|
||||
$nbt->setShort(self::TAG_AGE, $age);
|
||||
$nbt->setShort(self::TAG_PICKUP_DELAY, $this->pickupDelay);
|
||||
if($this->owner !== null){
|
||||
$nbt->setString("Owner", $this->owner);
|
||||
$nbt->setString(self::TAG_OWNER, $this->owner);
|
||||
}
|
||||
if($this->thrower !== null){
|
||||
$nbt->setString("Thrower", $this->thrower);
|
||||
$nbt->setString(self::TAG_THROWER, $this->thrower);
|
||||
}
|
||||
|
||||
return $nbt;
|
||||
|
@ -41,6 +41,13 @@ use pocketmine\world\World;
|
||||
use function ceil;
|
||||
|
||||
class Painting extends Entity{
|
||||
public const TAG_TILE_X = "TileX"; //TAG_Int
|
||||
public const TAG_TILE_Y = "TileY"; //TAG_Int
|
||||
public const TAG_TILE_Z = "TileZ"; //TAG_Int
|
||||
public const TAG_FACING_JE = "Facing"; //TAG_Byte
|
||||
public const TAG_DIRECTION_BE = "Direction"; //TAG_Byte
|
||||
public const TAG_MOTIVE = "Motive"; //TAG_String
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::PAINTING; }
|
||||
|
||||
public const DATA_TO_FACING = [
|
||||
@ -88,14 +95,14 @@ class Painting extends Entity{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setInt("TileX", (int) $this->blockIn->x);
|
||||
$nbt->setInt("TileY", (int) $this->blockIn->y);
|
||||
$nbt->setInt("TileZ", (int) $this->blockIn->z);
|
||||
$nbt->setInt(self::TAG_TILE_X, (int) $this->blockIn->x);
|
||||
$nbt->setInt(self::TAG_TILE_Y, (int) $this->blockIn->y);
|
||||
$nbt->setInt(self::TAG_TILE_Z, (int) $this->blockIn->z);
|
||||
|
||||
$nbt->setByte("Facing", self::FACING_TO_DATA[$this->facing]);
|
||||
$nbt->setByte("Direction", self::FACING_TO_DATA[$this->facing]); //Save both for full compatibility
|
||||
$nbt->setByte(self::TAG_FACING_JE, self::FACING_TO_DATA[$this->facing]);
|
||||
$nbt->setByte(self::TAG_DIRECTION_BE, self::FACING_TO_DATA[$this->facing]); //Save both for full compatibility
|
||||
|
||||
$nbt->setString("Motive", $this->motive->getName());
|
||||
$nbt->setString(self::TAG_MOTIVE, $this->motive->getName());
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ use pocketmine\world\Position;
|
||||
|
||||
class PrimedTNT extends Entity implements Explosive{
|
||||
|
||||
private const TAG_FUSE = "Fuse"; //TAG_Short
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::TNT; }
|
||||
|
||||
protected $gravity = 0.04;
|
||||
@ -81,7 +83,7 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
protected function initEntity(CompoundTag $nbt) : void{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->fuse = $nbt->getShort("Fuse", 80);
|
||||
$this->fuse = $nbt->getShort(self::TAG_FUSE, 80);
|
||||
}
|
||||
|
||||
public function canCollideWith(Entity $entity) : bool{
|
||||
@ -90,7 +92,7 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setShort("Fuse", $this->fuse);
|
||||
$nbt->setShort(self::TAG_FUSE, $this->fuse);
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ class Arrow extends Projectile{
|
||||
|
||||
private const TAG_PICKUP = "pickup"; //TAG_Byte
|
||||
public const TAG_CRIT = "crit"; //TAG_Byte
|
||||
private const TAG_LIFE = "life"; //TAG_Short
|
||||
|
||||
protected $gravity = 0.05;
|
||||
protected $drag = 0.01;
|
||||
@ -83,14 +84,14 @@ class Arrow extends Projectile{
|
||||
|
||||
$this->pickupMode = $nbt->getByte(self::TAG_PICKUP, self::PICKUP_ANY);
|
||||
$this->critical = $nbt->getByte(self::TAG_CRIT, 0) === 1;
|
||||
$this->collideTicks = $nbt->getShort("life", $this->collideTicks);
|
||||
$this->collideTicks = $nbt->getShort(self::TAG_LIFE, $this->collideTicks);
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setByte(self::TAG_PICKUP, $this->pickupMode);
|
||||
$nbt->setByte(self::TAG_CRIT, $this->critical ? 1 : 0);
|
||||
$nbt->setShort("life", $this->collideTicks);
|
||||
$nbt->setShort(self::TAG_LIFE, $this->collideTicks);
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,12 @@ use const M_PI;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
abstract class Projectile extends Entity{
|
||||
private const TAG_DAMAGE = "damage"; //TAG_Double
|
||||
private const TAG_TILE_X = "tileX"; //TAG_Int
|
||||
private const TAG_TILE_Y = "tileY"; //TAG_Int
|
||||
private const TAG_TILE_Z = "tileZ"; //TAG_Int
|
||||
private const TAG_BLOCK_ID = "blockId"; //TAG_Int
|
||||
private const TAG_BLOCK_DATA = "blockData"; //TAG_Byte
|
||||
|
||||
/** @var float */
|
||||
protected $damage = 0.0;
|
||||
@ -75,22 +81,22 @@ abstract class Projectile extends Entity{
|
||||
|
||||
$this->setMaxHealth(1);
|
||||
$this->setHealth(1);
|
||||
$this->damage = $nbt->getDouble("damage", $this->damage);
|
||||
$this->damage = $nbt->getDouble(self::TAG_DAMAGE, $this->damage);
|
||||
|
||||
(function() use ($nbt) : void{
|
||||
if(($tileXTag = $nbt->getTag("tileX")) instanceof IntTag && ($tileYTag = $nbt->getTag("tileY")) instanceof IntTag && ($tileZTag = $nbt->getTag("tileZ")) instanceof IntTag){
|
||||
if(($tileXTag = $nbt->getTag(self::TAG_TILE_X)) instanceof IntTag && ($tileYTag = $nbt->getTag(self::TAG_TILE_Y)) instanceof IntTag && ($tileZTag = $nbt->getTag(self::TAG_TILE_Z)) instanceof IntTag){
|
||||
$blockPos = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue());
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
|
||||
if(($blockIdTag = $nbt->getTag("blockId")) instanceof IntTag){
|
||||
if(($blockIdTag = $nbt->getTag(self::TAG_BLOCK_ID)) instanceof IntTag){
|
||||
$blockId = $blockIdTag->getValue();
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
|
||||
if(($blockDataTag = $nbt->getTag("blockData")) instanceof ByteTag){
|
||||
if(($blockDataTag = $nbt->getTag(self::TAG_BLOCK_DATA)) instanceof ByteTag){
|
||||
$blockData = $blockDataTag->getValue();
|
||||
}else{
|
||||
return;
|
||||
@ -134,17 +140,17 @@ abstract class Projectile extends Entity{
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setDouble("damage", $this->damage);
|
||||
$nbt->setDouble(self::TAG_DAMAGE, $this->damage);
|
||||
|
||||
if($this->blockHit !== null){
|
||||
$pos = $this->blockHit->getPosition();
|
||||
$nbt->setInt("tileX", $pos->x);
|
||||
$nbt->setInt("tileY", $pos->y);
|
||||
$nbt->setInt("tileZ", $pos->z);
|
||||
$nbt->setInt(self::TAG_TILE_X, $pos->x);
|
||||
$nbt->setInt(self::TAG_TILE_Y, $pos->y);
|
||||
$nbt->setInt(self::TAG_TILE_Z, $pos->z);
|
||||
|
||||
//we intentionally use different ones to PC because we don't have stringy IDs
|
||||
$nbt->setInt("blockId", $this->blockHit->getId());
|
||||
$nbt->setByte("blockData", $this->blockHit->getMeta());
|
||||
$nbt->setInt(self::TAG_BLOCK_ID, $this->blockHit->getId());
|
||||
$nbt->setByte(self::TAG_BLOCK_DATA, $this->blockHit->getMeta());
|
||||
}
|
||||
|
||||
return $nbt;
|
||||
|
@ -50,6 +50,8 @@ use function sqrt;
|
||||
|
||||
class SplashPotion extends Throwable{
|
||||
|
||||
public const TAG_POTION_ID = "PotionId"; //TAG_Short
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::SPLASH_POTION; }
|
||||
|
||||
protected $gravity = 0.05;
|
||||
@ -66,7 +68,7 @@ class SplashPotion extends Throwable{
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setShort("PotionId", PotionTypeIdMap::getInstance()->toId($this->getPotionType()));
|
||||
$nbt->setShort(self::TAG_POTION_ID, PotionTypeIdMap::getInstance()->toId($this->getPotionType()));
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
@ -98,17 +98,15 @@ class PlayerDeathEvent extends EntityDeathEvent{
|
||||
if($e instanceof Player){
|
||||
return KnownTranslationFactory::death_attack_player($name, $e->getDisplayName());
|
||||
}elseif($e instanceof Living){
|
||||
return KnownTranslationFactory::death_attack_mob($name, $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName());
|
||||
return KnownTranslationFactory::death_attack_mob($name, $e->getDisplayName());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EntityDamageEvent::CAUSE_PROJECTILE:
|
||||
if($deathCause instanceof EntityDamageByEntityEvent){
|
||||
$e = $deathCause->getDamager();
|
||||
if($e instanceof Player){
|
||||
if($e instanceof Living){
|
||||
return KnownTranslationFactory::death_attack_arrow($name, $e->getDisplayName());
|
||||
}elseif($e instanceof Living){
|
||||
return KnownTranslationFactory::death_attack_arrow($name, $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -149,10 +147,8 @@ class PlayerDeathEvent extends EntityDeathEvent{
|
||||
case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
|
||||
if($deathCause instanceof EntityDamageByEntityEvent){
|
||||
$e = $deathCause->getDamager();
|
||||
if($e instanceof Player){
|
||||
if($e instanceof Living){
|
||||
return KnownTranslationFactory::death_attack_explosion_player($name, $e->getDisplayName());
|
||||
}elseif($e instanceof Living){
|
||||
return KnownTranslationFactory::death_attack_explosion_player($name, $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName());
|
||||
}
|
||||
}
|
||||
return KnownTranslationFactory::death_attack_explosion($name);
|
||||
|
73
src/event/world/WorldParticleEvent.php
Normal file
73
src/event/world/WorldParticleEvent.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\particle\Particle;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class WorldParticleEvent extends WorldEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Player[] $recipients
|
||||
*/
|
||||
public function __construct(
|
||||
World $world,
|
||||
private Particle $particle,
|
||||
private Vector3 $position,
|
||||
private array $recipients
|
||||
){
|
||||
parent::__construct($world);
|
||||
}
|
||||
|
||||
public function getParticle() : Particle{
|
||||
return $this->particle;
|
||||
}
|
||||
|
||||
public function setParticle(Particle $particle) : void{
|
||||
$this->particle = $particle;
|
||||
}
|
||||
|
||||
public function getPosition() : Vector3{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Player[]
|
||||
*/
|
||||
public function getRecipients() : array{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player[] $recipients
|
||||
*/
|
||||
public function setRecipients(array $recipients) : void{
|
||||
$this->recipients = $recipients;
|
||||
}
|
||||
}
|
77
src/event/world/WorldSoundEvent.php
Normal file
77
src/event/world/WorldSoundEvent.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\world;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use pocketmine\world\World;
|
||||
|
||||
/**
|
||||
* Called when a sound is played in a world
|
||||
* @see World::addSound()
|
||||
*/
|
||||
class WorldSoundEvent extends WorldEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Player[] $recipients
|
||||
*/
|
||||
public function __construct(
|
||||
World $world,
|
||||
private Sound $sound,
|
||||
private Vector3 $position,
|
||||
private array $recipients
|
||||
){
|
||||
parent::__construct($world);
|
||||
}
|
||||
|
||||
public function getSound() : Sound{
|
||||
return $this->sound;
|
||||
}
|
||||
|
||||
public function setSound(Sound $sound) : void{
|
||||
$this->sound = $sound;
|
||||
}
|
||||
|
||||
public function getPosition() : Vector3{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Player[]
|
||||
*/
|
||||
public function getRecipients() : array{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Player[] $recipients
|
||||
*/
|
||||
public function setRecipients(array $recipients) : void{
|
||||
$this->recipients = $recipients;
|
||||
}
|
||||
}
|
@ -25,9 +25,9 @@ namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function json_decode;
|
||||
|
||||
final class CreativeInventory{
|
||||
@ -37,7 +37,7 @@ final class CreativeInventory{
|
||||
private array $creative = [];
|
||||
|
||||
private function __construct(){
|
||||
$creativeItems = json_decode(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json")), true);
|
||||
$creativeItems = json_decode(Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json")), true);
|
||||
|
||||
foreach($creativeItems as $data){
|
||||
$item = Item::jsonDeserialize($data);
|
||||
|
@ -59,12 +59,27 @@ class Item implements \JsonSerializable{
|
||||
use ItemEnchantmentHandlingTrait;
|
||||
|
||||
public const TAG_ENCH = "ench";
|
||||
private const TAG_ENCH_ID = "id"; //TAG_Short
|
||||
private const TAG_ENCH_LVL = "lvl"; //TAG_Short
|
||||
|
||||
public const TAG_DISPLAY = "display";
|
||||
public const TAG_BLOCK_ENTITY_TAG = "BlockEntityTag";
|
||||
|
||||
public const TAG_DISPLAY_NAME = "Name";
|
||||
public const TAG_DISPLAY_LORE = "Lore";
|
||||
|
||||
public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
|
||||
|
||||
private const TAG_ID = "id"; //TAG_Short
|
||||
private const TAG_COUNT = "Count"; //TAG_Byte
|
||||
private const TAG_DAMAGE = "Damage"; //TAG_Short
|
||||
private const TAG_TAG = "tag"; //TAG_Compound
|
||||
|
||||
public const TAG_SLOT = "Slot"; //TAG_Byte
|
||||
|
||||
private const TAG_CAN_PLACE_ON = "CanPlaceOn"; //TAG_List<TAG_String>
|
||||
private const TAG_CAN_DESTROY = "CanDestroy"; //TAG_List<TAG_String>
|
||||
|
||||
private ItemIdentifier $identifier;
|
||||
private CompoundTag $nbt;
|
||||
|
||||
@ -96,6 +111,8 @@ class Item implements \JsonSerializable{
|
||||
*/
|
||||
protected $canDestroy;
|
||||
|
||||
protected bool $keepOnDeath = false;
|
||||
|
||||
/**
|
||||
* Constructs a new Item type. This constructor should ONLY be used when constructing a new item TYPE to register
|
||||
* into the index.
|
||||
@ -222,6 +239,17 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether players will retain this item on death. If a non-player dies it will be excluded from the drops.
|
||||
*/
|
||||
public function keepOnDeath() : bool{
|
||||
return $this->keepOnDeath;
|
||||
}
|
||||
|
||||
public function setKeepOnDeath(bool $keepOnDeath) : void{
|
||||
$this->keepOnDeath = $keepOnDeath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this Item has a non-empty NBT.
|
||||
*/
|
||||
@ -290,8 +318,8 @@ class Item implements \JsonSerializable{
|
||||
if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){
|
||||
/** @var CompoundTag $enchantment */
|
||||
foreach($enchantments as $enchantment){
|
||||
$magicNumber = $enchantment->getShort("id", -1);
|
||||
$level = $enchantment->getShort("lvl", 0);
|
||||
$magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
|
||||
$level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
|
||||
if($level <= 0){
|
||||
continue;
|
||||
}
|
||||
@ -305,7 +333,7 @@ class Item implements \JsonSerializable{
|
||||
$this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
|
||||
|
||||
$this->canPlaceOn = [];
|
||||
$canPlaceOn = $tag->getListTag("CanPlaceOn");
|
||||
$canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON);
|
||||
if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){
|
||||
/** @var StringTag $entry */
|
||||
foreach($canPlaceOn as $entry){
|
||||
@ -313,13 +341,15 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
}
|
||||
$this->canDestroy = [];
|
||||
$canDestroy = $tag->getListTag("CanDestroy");
|
||||
$canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY);
|
||||
if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){
|
||||
/** @var StringTag $entry */
|
||||
foreach($canDestroy as $entry){
|
||||
$this->canDestroy[$entry->getValue()] = $entry->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
$this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
|
||||
}
|
||||
|
||||
protected function serializeCompoundTag(CompoundTag $tag) : void{
|
||||
@ -346,8 +376,8 @@ class Item implements \JsonSerializable{
|
||||
$ench = new ListTag();
|
||||
foreach($this->getEnchantments() as $enchantmentInstance){
|
||||
$ench->push(CompoundTag::create()
|
||||
->setShort("id", EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType()))
|
||||
->setShort("lvl", $enchantmentInstance->getLevel())
|
||||
->setShort(self::TAG_ENCH_ID, EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType()))
|
||||
->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
|
||||
);
|
||||
}
|
||||
$tag->setTag(self::TAG_ENCH, $ench);
|
||||
@ -364,18 +394,24 @@ class Item implements \JsonSerializable{
|
||||
foreach($this->canPlaceOn as $item){
|
||||
$canPlaceOn->push(new StringTag($item));
|
||||
}
|
||||
$tag->setTag("CanPlaceOn", $canPlaceOn);
|
||||
$tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
|
||||
}else{
|
||||
$tag->removeTag("CanPlaceOn");
|
||||
$tag->removeTag(self::TAG_CAN_PLACE_ON);
|
||||
}
|
||||
if(count($this->canDestroy) > 0){
|
||||
$canDestroy = new ListTag();
|
||||
foreach($this->canDestroy as $item){
|
||||
$canDestroy->push(new StringTag($item));
|
||||
}
|
||||
$tag->setTag("CanDestroy", $canDestroy);
|
||||
$tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
|
||||
}else{
|
||||
$tag->removeTag("CanDestroy");
|
||||
$tag->removeTag(self::TAG_CAN_DESTROY);
|
||||
}
|
||||
|
||||
if($this->keepOnDeath){
|
||||
$tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
|
||||
}else{
|
||||
$tag->removeTag(self::TAG_KEEP_ON_DEATH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,6 +590,16 @@ class Item implements \JsonSerializable{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a player uses the item to interact with entity, for example by using a name tag.
|
||||
*
|
||||
* @param Vector3 $clickVector The exact position of the click (absolute coordinates)
|
||||
* @return bool whether some action took place
|
||||
*/
|
||||
public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ticks a player must wait before activating this item again.
|
||||
*/
|
||||
@ -655,17 +701,17 @@ class Item implements \JsonSerializable{
|
||||
*/
|
||||
public function nbtSerialize(int $slot = -1) : CompoundTag{
|
||||
$result = CompoundTag::create()
|
||||
->setShort("id", $this->getId())
|
||||
->setByte("Count", Binary::signByte($this->count))
|
||||
->setShort("Damage", $this->getMeta());
|
||||
->setShort(self::TAG_ID, $this->getId())
|
||||
->setByte(self::TAG_COUNT, Binary::signByte($this->count))
|
||||
->setShort(self::TAG_DAMAGE, $this->getMeta());
|
||||
|
||||
$tag = $this->getNamedTag();
|
||||
if($tag->count() > 0){
|
||||
$result->setTag("tag", $tag);
|
||||
$result->setTag(self::TAG_TAG, $tag);
|
||||
}
|
||||
|
||||
if($slot !== -1){
|
||||
$result->setByte("Slot", $slot);
|
||||
$result->setByte(self::TAG_SLOT, $slot);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -677,14 +723,14 @@ class Item implements \JsonSerializable{
|
||||
* @throws SavedDataLoadingException
|
||||
*/
|
||||
public static function nbtDeserialize(CompoundTag $tag) : Item{
|
||||
if($tag->getTag("id") === null || $tag->getTag("Count") === null){
|
||||
if($tag->getTag(self::TAG_ID) === null || $tag->getTag(self::TAG_COUNT) === null){
|
||||
return VanillaItems::AIR();
|
||||
}
|
||||
|
||||
$count = Binary::unsignByte($tag->getByte("Count"));
|
||||
$meta = $tag->getShort("Damage", 0);
|
||||
$count = Binary::unsignByte($tag->getByte(self::TAG_COUNT));
|
||||
$meta = $tag->getShort(self::TAG_DAMAGE, 0);
|
||||
|
||||
$idTag = $tag->getTag("id");
|
||||
$idTag = $tag->getTag(self::TAG_ID);
|
||||
if($idTag instanceof ShortTag){
|
||||
$item = ItemFactory::getInstance()->get($idTag->getValue(), $meta, $count);
|
||||
}elseif($idTag instanceof StringTag){ //PC item save format
|
||||
@ -699,7 +745,7 @@ class Item implements \JsonSerializable{
|
||||
throw new SavedDataLoadingException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given");
|
||||
}
|
||||
|
||||
$itemNBT = $tag->getCompoundTag("tag");
|
||||
$itemNBT = $tag->getCompoundTag(self::TAG_TAG);
|
||||
if($itemNBT !== null){
|
||||
$item->setNamedTag(clone $itemNBT);
|
||||
}
|
||||
|
@ -24,11 +24,10 @@ declare(strict_types=1);
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function explode;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_numeric;
|
||||
@ -53,7 +52,7 @@ final class LegacyStringToItemParser{
|
||||
private static function make() : self{
|
||||
$result = new self(ItemFactory::getInstance());
|
||||
|
||||
$mappingsRaw = Utils::assumeNotFalse(@file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, 'item_from_string_bc_map.json')), "Missing required resource file");
|
||||
$mappingsRaw = Filesystem::fileGetContents(Path::join(\pocketmine\RESOURCE_PATH, 'item_from_string_bc_map.json'));
|
||||
|
||||
$mappings = json_decode($mappingsRaw, true);
|
||||
if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array");
|
||||
|
@ -53,6 +53,7 @@ final class StringToEnchantmentParser extends StringToTParser{
|
||||
$result->register("respiration", fn() => VanillaEnchantments::RESPIRATION());
|
||||
$result->register("sharpness", fn() => VanillaEnchantments::SHARPNESS());
|
||||
$result->register("silk_touch", fn() => VanillaEnchantments::SILK_TOUCH());
|
||||
$result->register("swift_sneak", fn() => VanillaEnchantments::SWIFT_SNEAK());
|
||||
$result->register("thorns", fn() => VanillaEnchantments::THORNS());
|
||||
$result->register("unbreaking", fn() => VanillaEnchantments::UNBREAKING());
|
||||
$result->register("vanishing", fn() => VanillaEnchantments::VANISHING());
|
||||
|
@ -49,6 +49,7 @@ use pocketmine\utils\RegistryTrait;
|
||||
* @method static Enchantment RESPIRATION()
|
||||
* @method static SharpnessEnchantment SHARPNESS()
|
||||
* @method static Enchantment SILK_TOUCH()
|
||||
* @method static Enchantment SWIFT_SNEAK()
|
||||
* @method static Enchantment THORNS()
|
||||
* @method static Enchantment UNBREAKING()
|
||||
* @method static Enchantment VANISHING()
|
||||
@ -95,6 +96,8 @@ final class VanillaEnchantments{
|
||||
self::register("MENDING", new Enchantment(KnownTranslationFactory::enchantment_mending(), Rarity::RARE, ItemFlags::NONE, ItemFlags::ALL, 1));
|
||||
|
||||
self::register("VANISHING", new Enchantment(KnownTranslationFactory::enchantment_curse_vanishing(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::ALL, 1));
|
||||
|
||||
self::register("SWIFT_SNEAK", new Enchantment(KnownTranslationFactory::enchantment_swift_sneak(), Rarity::MYTHIC, ItemFlags::NONE, ItemFlags::LEGS, 3));
|
||||
}
|
||||
|
||||
protected static function register(string $name, Enchantment $member) : void{
|
||||
|
@ -661,6 +661,12 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_fireworks(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_FIREWORKS, [
|
||||
0 => $param0,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_generic(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
|
||||
0 => $param0,
|
||||
@ -760,6 +766,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_INVALIDSKIN, []);
|
||||
}
|
||||
|
||||
public static function disconnectionScreen_loggedinOtherLocation() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_LOGGEDINOTHERLOCATION, []);
|
||||
}
|
||||
|
||||
public static function disconnectionScreen_noReason() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_NOREASON, []);
|
||||
}
|
||||
@ -1166,6 +1176,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_dumpmemory_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_DUMPMEMORY_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_effect_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_EFFECT_DESCRIPTION, []);
|
||||
}
|
||||
@ -1574,6 +1588,51 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DEBUG_ENABLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_ban(Translatable|string $reason) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_BAN, [
|
||||
"reason" => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_ban_hardcore() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_BAN_HARDCORE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_ban_ip() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_BAN_IP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_ban_noReason() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_BAN_NOREASON, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error(Translatable|string $error, Translatable|string $errorId) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR, [
|
||||
"error" => $error,
|
||||
"errorId" => $errorId,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error_authentication() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_AUTHENTICATION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error_badPacket() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_BADPACKET, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error_internal() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_INTERNAL, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error_loginTimeout() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_LOGINTIMEOUT, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_error_respawn() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_RESPAWN, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_incompatibleProtocol(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_INCOMPATIBLEPROTOCOL, [
|
||||
0 => $param0,
|
||||
@ -1602,6 +1661,28 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_TOOLATE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_kick(Translatable|string $reason) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_KICK, [
|
||||
"reason" => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_kick_noReason() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_KICK_NOREASON, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_transfer() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_TRANSFER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_whitelisted() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_WHITELISTED, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_disconnect_xblImpersonation() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_XBLIMPERSONATION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_level_ambiguousFormat(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_LEVEL_AMBIGUOUSFORMAT, [
|
||||
0 => $param0,
|
||||
@ -1707,6 +1788,274 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_network_session_close(Translatable|string $reason) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_NETWORK_SESSION_CLOSE, [
|
||||
"reason" => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_network_session_open() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_NETWORK_SESSION_OPEN, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_network_session_playerName(Translatable|string $playerName) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_NETWORK_SESSION_PLAYERNAME, [
|
||||
"playerName" => $playerName,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_broadcast_admin() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_BROADCAST_ADMIN, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_broadcast_user() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_BROADCAST_USER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_ban_ip() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_BAN_IP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_ban_list() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_BAN_LIST, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_ban_player() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_BAN_PLAYER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_clear_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CLEAR_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_clear_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CLEAR_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_defaultgamemode() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_DEFAULTGAMEMODE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_difficulty() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_DIFFICULTY, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_dumpmemory() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_DUMPMEMORY, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_effect_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_EFFECT_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_effect_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_EFFECT_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_enchant_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_ENCHANT_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_enchant_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_ENCHANT_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_gamemode_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_GAMEMODE_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_gamemode_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_GAMEMODE_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_gc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_GC, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_give_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_GIVE_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_give_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_GIVE_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_help() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_HELP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_kick() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_KICK, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_kill_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_KILL_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_kill_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_KILL_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_list() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_LIST, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_me() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_ME, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_op_give() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_OP_GIVE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_op_take() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_OP_TAKE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_particle() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_PARTICLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_plugins() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_PLUGINS, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_save_disable() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SAVE_DISABLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_save_enable() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SAVE_ENABLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_save_perform() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SAVE_PERFORM, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_say() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SAY, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_seed() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SEED, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_setworldspawn() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SETWORLDSPAWN, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_spawnpoint_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SPAWNPOINT_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_spawnpoint_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_SPAWNPOINT_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_status() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_STATUS, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_stop() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_STOP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_teleport_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TELEPORT_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_teleport_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TELEPORT_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_tell() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TELL, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_time_add() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIME_ADD, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_time_query() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIME_QUERY, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_time_set() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIME_SET, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_time_start() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIME_START, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_time_stop() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIME_STOP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_timings() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TIMINGS, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_title_other() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TITLE_OTHER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_title_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TITLE_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_transferserver() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_TRANSFERSERVER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_unban_ip() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_UNBAN_IP, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_unban_player() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_UNBAN_PLAYER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_version() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_VERSION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_add() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_ADD, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_disable() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_DISABLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_enable() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_ENABLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_list() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_LIST, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_reload() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_RELOAD, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_whitelist_remove() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_group_console() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_CONSOLE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_group_operator() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_OPERATOR, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_group_user() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_GROUP_USER, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_player_invalidEntity(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PLAYER_INVALIDENTITY, [
|
||||
0 => $param0,
|
||||
@ -2241,6 +2590,12 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::QUERY_WARNING2, []);
|
||||
}
|
||||
|
||||
public static function record_nowPlaying(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::RECORD_NOWPLAYING, [
|
||||
0 => $param0,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function server_port() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::SERVER_PORT, []);
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ final class KnownTranslationKeys{
|
||||
public const DEATH_ATTACK_EXPLOSION_PLAYER = "death.attack.explosion.player";
|
||||
public const DEATH_ATTACK_FALL = "death.attack.fall";
|
||||
public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
|
||||
public const DEATH_ATTACK_FIREWORKS = "death.attack.fireworks";
|
||||
public const DEATH_ATTACK_GENERIC = "death.attack.generic";
|
||||
public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
|
||||
public const DEATH_ATTACK_INWALL = "death.attack.inWall";
|
||||
@ -163,6 +164,7 @@ final class KnownTranslationKeys{
|
||||
public const DEFAULT_VALUES_INFO = "default_values_info";
|
||||
public const DISCONNECTIONSCREEN_INVALIDNAME = "disconnectionScreen.invalidName";
|
||||
public const DISCONNECTIONSCREEN_INVALIDSKIN = "disconnectionScreen.invalidSkin";
|
||||
public const DISCONNECTIONSCREEN_LOGGEDINOTHERLOCATION = "disconnectionScreen.loggedinOtherLocation";
|
||||
public const DISCONNECTIONSCREEN_NOREASON = "disconnectionScreen.noReason";
|
||||
public const DISCONNECTIONSCREEN_NOTAUTHENTICATED = "disconnectionScreen.notAuthenticated";
|
||||
public const DISCONNECTIONSCREEN_OUTDATEDCLIENT = "disconnectionScreen.outdatedClient";
|
||||
@ -258,6 +260,7 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_COMMAND_DEFAULTGAMEMODE_DESCRIPTION = "pocketmine.command.defaultgamemode.description";
|
||||
public const POCKETMINE_COMMAND_DEOP_DESCRIPTION = "pocketmine.command.deop.description";
|
||||
public const POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION = "pocketmine.command.difficulty.description";
|
||||
public const POCKETMINE_COMMAND_DUMPMEMORY_DESCRIPTION = "pocketmine.command.dumpmemory.description";
|
||||
public const POCKETMINE_COMMAND_EFFECT_DESCRIPTION = "pocketmine.command.effect.description";
|
||||
public const POCKETMINE_COMMAND_ENCHANT_DESCRIPTION = "pocketmine.command.enchant.description";
|
||||
public const POCKETMINE_COMMAND_ERROR_PERMISSION = "pocketmine.command.error.permission";
|
||||
@ -342,12 +345,27 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_DATA_PLAYEROLD = "pocketmine.data.playerOld";
|
||||
public const POCKETMINE_DATA_SAVEERROR = "pocketmine.data.saveError";
|
||||
public const POCKETMINE_DEBUG_ENABLE = "pocketmine.debug.enable";
|
||||
public const POCKETMINE_DISCONNECT_BAN = "pocketmine.disconnect.ban";
|
||||
public const POCKETMINE_DISCONNECT_BAN_HARDCORE = "pocketmine.disconnect.ban.hardcore";
|
||||
public const POCKETMINE_DISCONNECT_BAN_IP = "pocketmine.disconnect.ban.ip";
|
||||
public const POCKETMINE_DISCONNECT_BAN_NOREASON = "pocketmine.disconnect.ban.noReason";
|
||||
public const POCKETMINE_DISCONNECT_ERROR = "pocketmine.disconnect.error";
|
||||
public const POCKETMINE_DISCONNECT_ERROR_AUTHENTICATION = "pocketmine.disconnect.error.authentication";
|
||||
public const POCKETMINE_DISCONNECT_ERROR_BADPACKET = "pocketmine.disconnect.error.badPacket";
|
||||
public const POCKETMINE_DISCONNECT_ERROR_INTERNAL = "pocketmine.disconnect.error.internal";
|
||||
public const POCKETMINE_DISCONNECT_ERROR_LOGINTIMEOUT = "pocketmine.disconnect.error.loginTimeout";
|
||||
public const POCKETMINE_DISCONNECT_ERROR_RESPAWN = "pocketmine.disconnect.error.respawn";
|
||||
public const POCKETMINE_DISCONNECT_INCOMPATIBLEPROTOCOL = "pocketmine.disconnect.incompatibleProtocol";
|
||||
public const POCKETMINE_DISCONNECT_INVALIDSESSION = "pocketmine.disconnect.invalidSession";
|
||||
public const POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE = "pocketmine.disconnect.invalidSession.badSignature";
|
||||
public const POCKETMINE_DISCONNECT_INVALIDSESSION_MISSINGKEY = "pocketmine.disconnect.invalidSession.missingKey";
|
||||
public const POCKETMINE_DISCONNECT_INVALIDSESSION_TOOEARLY = "pocketmine.disconnect.invalidSession.tooEarly";
|
||||
public const POCKETMINE_DISCONNECT_INVALIDSESSION_TOOLATE = "pocketmine.disconnect.invalidSession.tooLate";
|
||||
public const POCKETMINE_DISCONNECT_KICK = "pocketmine.disconnect.kick";
|
||||
public const POCKETMINE_DISCONNECT_KICK_NOREASON = "pocketmine.disconnect.kick.noReason";
|
||||
public const POCKETMINE_DISCONNECT_TRANSFER = "pocketmine.disconnect.transfer";
|
||||
public const POCKETMINE_DISCONNECT_WHITELISTED = "pocketmine.disconnect.whitelisted";
|
||||
public const POCKETMINE_DISCONNECT_XBLIMPERSONATION = "pocketmine.disconnect.xblImpersonation";
|
||||
public const POCKETMINE_LEVEL_AMBIGUOUSFORMAT = "pocketmine.level.ambiguousFormat";
|
||||
public const POCKETMINE_LEVEL_BACKGROUNDGENERATION = "pocketmine.level.backgroundGeneration";
|
||||
public const POCKETMINE_LEVEL_BADDEFAULTFORMAT = "pocketmine.level.badDefaultFormat";
|
||||
@ -365,6 +383,72 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_LEVEL_UNKNOWNGENERATOR = "pocketmine.level.unknownGenerator";
|
||||
public const POCKETMINE_LEVEL_UNLOADING = "pocketmine.level.unloading";
|
||||
public const POCKETMINE_LEVEL_UNSUPPORTEDFORMAT = "pocketmine.level.unsupportedFormat";
|
||||
public const POCKETMINE_NETWORK_SESSION_CLOSE = "pocketmine.network.session.close";
|
||||
public const POCKETMINE_NETWORK_SESSION_OPEN = "pocketmine.network.session.open";
|
||||
public const POCKETMINE_NETWORK_SESSION_PLAYERNAME = "pocketmine.network.session.playerName";
|
||||
public const POCKETMINE_PERMISSION_BROADCAST_ADMIN = "pocketmine.permission.broadcast.admin";
|
||||
public const POCKETMINE_PERMISSION_BROADCAST_USER = "pocketmine.permission.broadcast.user";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_BAN_IP = "pocketmine.permission.command.ban.ip";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_BAN_LIST = "pocketmine.permission.command.ban.list";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_BAN_PLAYER = "pocketmine.permission.command.ban.player";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CLEAR_OTHER = "pocketmine.permission.command.clear.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CLEAR_SELF = "pocketmine.permission.command.clear.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DEFAULTGAMEMODE = "pocketmine.permission.command.defaultgamemode";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DIFFICULTY = "pocketmine.permission.command.difficulty";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DUMPMEMORY = "pocketmine.permission.command.dumpmemory";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_EFFECT_OTHER = "pocketmine.permission.command.effect.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_EFFECT_SELF = "pocketmine.permission.command.effect.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_ENCHANT_OTHER = "pocketmine.permission.command.enchant.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_ENCHANT_SELF = "pocketmine.permission.command.enchant.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_GAMEMODE_OTHER = "pocketmine.permission.command.gamemode.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_GAMEMODE_SELF = "pocketmine.permission.command.gamemode.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_GC = "pocketmine.permission.command.gc";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_GIVE_OTHER = "pocketmine.permission.command.give.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_GIVE_SELF = "pocketmine.permission.command.give.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_HELP = "pocketmine.permission.command.help";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_KICK = "pocketmine.permission.command.kick";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_KILL_OTHER = "pocketmine.permission.command.kill.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_KILL_SELF = "pocketmine.permission.command.kill.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_LIST = "pocketmine.permission.command.list";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_ME = "pocketmine.permission.command.me";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_OP_GIVE = "pocketmine.permission.command.op.give";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_OP_TAKE = "pocketmine.permission.command.op.take";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_PARTICLE = "pocketmine.permission.command.particle";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_PLUGINS = "pocketmine.permission.command.plugins";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SAVE_DISABLE = "pocketmine.permission.command.save.disable";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SAVE_ENABLE = "pocketmine.permission.command.save.enable";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SAVE_PERFORM = "pocketmine.permission.command.save.perform";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SAY = "pocketmine.permission.command.say";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SEED = "pocketmine.permission.command.seed";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SETWORLDSPAWN = "pocketmine.permission.command.setworldspawn";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SPAWNPOINT_OTHER = "pocketmine.permission.command.spawnpoint.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_SPAWNPOINT_SELF = "pocketmine.permission.command.spawnpoint.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_STATUS = "pocketmine.permission.command.status";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_STOP = "pocketmine.permission.command.stop";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TELEPORT_OTHER = "pocketmine.permission.command.teleport.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TELEPORT_SELF = "pocketmine.permission.command.teleport.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TELL = "pocketmine.permission.command.tell";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIME_ADD = "pocketmine.permission.command.time.add";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIME_QUERY = "pocketmine.permission.command.time.query";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIME_SET = "pocketmine.permission.command.time.set";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIME_START = "pocketmine.permission.command.time.start";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIME_STOP = "pocketmine.permission.command.time.stop";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TIMINGS = "pocketmine.permission.command.timings";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TITLE_OTHER = "pocketmine.permission.command.title.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TITLE_SELF = "pocketmine.permission.command.title.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_TRANSFERSERVER = "pocketmine.permission.command.transferserver";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_UNBAN_IP = "pocketmine.permission.command.unban.ip";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_UNBAN_PLAYER = "pocketmine.permission.command.unban.player";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_VERSION = "pocketmine.permission.command.version";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_ADD = "pocketmine.permission.command.whitelist.add";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_DISABLE = "pocketmine.permission.command.whitelist.disable";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_ENABLE = "pocketmine.permission.command.whitelist.enable";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_LIST = "pocketmine.permission.command.whitelist.list";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_RELOAD = "pocketmine.permission.command.whitelist.reload";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_REMOVE = "pocketmine.permission.command.whitelist.remove";
|
||||
public const POCKETMINE_PERMISSION_GROUP_CONSOLE = "pocketmine.permission.group.console";
|
||||
public const POCKETMINE_PERMISSION_GROUP_OPERATOR = "pocketmine.permission.group.operator";
|
||||
public const POCKETMINE_PERMISSION_GROUP_USER = "pocketmine.permission.group.user";
|
||||
public const POCKETMINE_PLAYER_INVALIDENTITY = "pocketmine.player.invalidEntity";
|
||||
public const POCKETMINE_PLAYER_INVALIDMOVE = "pocketmine.player.invalidMove";
|
||||
public const POCKETMINE_PLAYER_LOGIN = "pocketmine.player.logIn";
|
||||
@ -466,6 +550,7 @@ final class KnownTranslationKeys{
|
||||
public const QUERY_DISABLE = "query_disable";
|
||||
public const QUERY_WARNING1 = "query_warning1";
|
||||
public const QUERY_WARNING2 = "query_warning2";
|
||||
public const RECORD_NOWPLAYING = "record.nowPlaying";
|
||||
public const SERVER_PORT = "server_port";
|
||||
public const SERVER_PORT_V4 = "server_port_v4";
|
||||
public const SERVER_PORT_V6 = "server_port_v6";
|
||||
|
@ -34,7 +34,9 @@ use function is_dir;
|
||||
use function ord;
|
||||
use function parse_ini_file;
|
||||
use function scandir;
|
||||
use function str_ends_with;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
@ -62,7 +64,7 @@ class Language{
|
||||
|
||||
if($allFiles !== false){
|
||||
$files = array_filter($allFiles, function(string $filename) : bool{
|
||||
return substr($filename, -4) === ".ini";
|
||||
return str_ends_with($filename, ".ini");
|
||||
});
|
||||
|
||||
$result = [];
|
||||
@ -71,8 +73,8 @@ class Language{
|
||||
try{
|
||||
$code = explode(".", $file)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings["language.name"])){
|
||||
$result[$code] = $strings["language.name"];
|
||||
if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
|
||||
$result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];
|
||||
}
|
||||
}catch(LanguageNotFoundException $e){
|
||||
// no-op
|
||||
@ -142,8 +144,10 @@ class Language{
|
||||
* @param (float|int|string|Translatable)[] $params
|
||||
*/
|
||||
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{
|
||||
$baseText = $this->get($str);
|
||||
$baseText = $this->parseTranslation(($onlyPrefix === null || strpos($str, $onlyPrefix) === 0) ? $baseText : $str, $onlyPrefix);
|
||||
$baseText = ($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $this->internalGet($str) : null;
|
||||
if($baseText === null){ //key not found, embedded inside format string, or doesn't match prefix
|
||||
$baseText = $this->parseTranslation($str, $onlyPrefix);
|
||||
}
|
||||
|
||||
foreach($params as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
|
||||
@ -155,7 +159,9 @@ class Language{
|
||||
|
||||
public function translate(Translatable $c) : string{
|
||||
$baseText = $this->internalGet($c->getText());
|
||||
$baseText = $this->parseTranslation($baseText ?? $c->getText());
|
||||
if($baseText === null){ //key not found or embedded inside format string
|
||||
$baseText = $this->parseTranslation($c->getText());
|
||||
}
|
||||
|
||||
foreach($c->getParameters() as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
|
||||
@ -181,6 +187,19 @@ class Language{
|
||||
return $this->lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces translation keys embedded inside a string with their raw values.
|
||||
* Embedded translation keys must be prefixed by a "%" character.
|
||||
*
|
||||
* This is used to allow the "text" field of a Translatable to contain formatting (e.g. colour codes) and
|
||||
* multiple embedded translation keys.
|
||||
*
|
||||
* Normal translations whose "text" is just a single translation key don't need to use this method, and can be
|
||||
* processed via get() directly.
|
||||
*
|
||||
* @param string|null $onlyPrefix If non-null, only translation keys with this prefix will be replaced. This is
|
||||
* used to allow a client to do its own translating of vanilla strings.
|
||||
*/
|
||||
protected function parseTranslation(string $text, ?string $onlyPrefix = null) : string{
|
||||
$newString = "";
|
||||
|
||||
|
@ -456,6 +456,9 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
|
||||
if(!$this->connected){
|
||||
return false;
|
||||
}
|
||||
//Basic safety restriction. TODO: improve this
|
||||
if(!$this->loggedIn && !$packet->canBeSentBeforeLogin()){
|
||||
throw new \InvalidArgumentException("Attempted to send " . get_class($packet) . " to " . $this->getDisplayName() . " too early");
|
||||
@ -573,28 +576,37 @@ class NetworkSession{
|
||||
$this->disconnectGuard = true;
|
||||
$func();
|
||||
$this->disconnectGuard = false;
|
||||
$this->flushSendBuffer(true);
|
||||
$this->sender->close("");
|
||||
foreach($this->disposeHooks as $callback){
|
||||
$callback();
|
||||
}
|
||||
$this->disposeHooks->clear();
|
||||
$this->setHandler(null);
|
||||
$this->connected = false;
|
||||
$this->manager->remove($this);
|
||||
$this->logger->info("Session closed due to $reason");
|
||||
|
||||
$this->invManager = null; //break cycles - TODO: this really ought to be deferred until it's safe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs actions after the session has been disconnected. By this point, nothing should be interacting with the
|
||||
* session, so it's safe to destroy any cycles and perform destructive cleanup.
|
||||
*/
|
||||
private function dispose() : void{
|
||||
$this->invManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the session, destroying the associated player (if it exists).
|
||||
*/
|
||||
public function disconnect(string $reason, bool $notify = true) : void{
|
||||
$this->tryDisconnect(function() use ($reason, $notify) : void{
|
||||
if($notify){
|
||||
$this->sendDataPacket(DisconnectPacket::create($reason));
|
||||
}
|
||||
if($this->player !== null){
|
||||
$this->player->onPostDisconnect($reason, null);
|
||||
}
|
||||
$this->doServerDisconnect($reason, $notify);
|
||||
}, $reason);
|
||||
}
|
||||
|
||||
@ -607,7 +619,6 @@ class NetworkSession{
|
||||
if($this->player !== null){
|
||||
$this->player->onPostDisconnect($reason, null);
|
||||
}
|
||||
$this->doServerDisconnect($reason, false);
|
||||
}, $reason);
|
||||
}
|
||||
|
||||
@ -616,21 +627,10 @@ class NetworkSession{
|
||||
*/
|
||||
public function onPlayerDestroyed(string $reason) : void{
|
||||
$this->tryDisconnect(function() use ($reason) : void{
|
||||
$this->doServerDisconnect($reason, true);
|
||||
$this->sendDataPacket(DisconnectPacket::create($reason));
|
||||
}, $reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper function used to handle server disconnections.
|
||||
*/
|
||||
private function doServerDisconnect(string $reason, bool $notify = true) : void{
|
||||
if($notify){
|
||||
$this->sendDataPacket(DisconnectPacket::create($reason !== "" ? $reason : null), true);
|
||||
}
|
||||
|
||||
$this->sender->close($notify ? $reason : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the network interface to close the session when the client disconnects without server input, for
|
||||
* example in a timeout condition or voluntary client disconnect.
|
||||
@ -714,7 +714,7 @@ class NetworkSession{
|
||||
//TODO: we shouldn't be loading player data here at all, but right now we don't have any choice :(
|
||||
$this->cachedOfflinePlayerData = $this->server->getOfflinePlayerData($this->info->getUsername());
|
||||
if($checkXUID){
|
||||
$recordedXUID = $this->cachedOfflinePlayerData !== null ? $this->cachedOfflinePlayerData->getTag("LastKnownXUID") : null;
|
||||
$recordedXUID = $this->cachedOfflinePlayerData !== null ? $this->cachedOfflinePlayerData->getTag(Player::TAG_LAST_KNOWN_XUID) : null;
|
||||
if(!($recordedXUID instanceof StringTag)){
|
||||
$this->logger->debug("No previous XUID recorded, no choice but to trust this player");
|
||||
}elseif(!$kickForXUIDMismatch($recordedXUID->getValue())){
|
||||
@ -775,9 +775,9 @@ class NetworkSession{
|
||||
$this->setHandler(new InGamePacketHandler($this->player, $this, $this->invManager));
|
||||
}
|
||||
|
||||
public function onServerDeath() : void{
|
||||
public function onServerDeath(Translatable|string $deathMessage) : void{
|
||||
if($this->handler instanceof InGamePacketHandler){ //TODO: this is a bad fix for pre-spawn death, this shouldn't be reachable at all at this stage :(
|
||||
$this->setHandler(new DeathPacketHandler($this->player, $this, $this->invManager ?? throw new AssumptionFailedError()));
|
||||
$this->setHandler(new DeathPacketHandler($this->player, $this, $this->invManager ?? throw new AssumptionFailedError(), $deathMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@ -852,7 +852,7 @@ class NetworkSession{
|
||||
UpdateAbilitiesPacketLayer::ABILITY_FLYING => $for->isFlying(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_OPERATOR => $isOp,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_INVULNERABLE => $for->isCreative(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_MUTED => false,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_WORLD_BUILDER => false,
|
||||
@ -962,15 +962,19 @@ class NetworkSession{
|
||||
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));
|
||||
}
|
||||
|
||||
public function onRawChatMessage(string $message) : void{
|
||||
$this->sendDataPacket(TextPacket::raw($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $parameters
|
||||
*/
|
||||
public function onTranslatedChatMessage(string $key, array $parameters) : void{
|
||||
$this->sendDataPacket(TextPacket::translation($key, $parameters));
|
||||
public function onChatMessage(Translatable|string $message) : void{
|
||||
if($message instanceof Translatable){
|
||||
$language = $this->player->getLanguage();
|
||||
if(!$this->server->isLanguageForced()){
|
||||
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
|
||||
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters());
|
||||
$this->sendDataPacket(TextPacket::translation($language->translateString($message->getText(), $parameters, "pocketmine."), $parameters));
|
||||
}else{
|
||||
$this->sendDataPacket(TextPacket::raw($language->translate($message)));
|
||||
}
|
||||
}else{
|
||||
$this->sendDataPacket(TextPacket::raw($message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1139,6 +1143,11 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
if(!$this->isConnected()){
|
||||
$this->dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->info === null){
|
||||
if(time() >= $this->connectTime + 10){
|
||||
$this->disconnect("Login timeout");
|
||||
|
6
src/network/mcpe/cache/StaticPacketCache.php
vendored
6
src/network/mcpe/cache/StaticPacketCache.php
vendored
@ -27,9 +27,9 @@ use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
|
||||
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
class StaticPacketCache{
|
||||
use SingletonTrait;
|
||||
@ -38,9 +38,7 @@ class StaticPacketCache{
|
||||
* @phpstan-return CacheableNbt<\pocketmine\nbt\tag\CompoundTag>
|
||||
*/
|
||||
private static function loadCompoundFromFile(string $filePath) : CacheableNbt{
|
||||
$rawNbt = @file_get_contents($filePath);
|
||||
if($rawNbt === false) throw new \RuntimeException("Failed to read file");
|
||||
return new CacheableNbt((new NetworkNbtSerializer())->read($rawNbt)->mustGetCompoundTag());
|
||||
return new CacheableNbt((new NetworkNbtSerializer())->read(Filesystem::fileGetContents($filePath))->mustGetCompoundTag());
|
||||
}
|
||||
|
||||
private static function make() : self{
|
||||
|
@ -26,10 +26,9 @@ namespace pocketmine\network\mcpe\convert;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
@ -40,7 +39,7 @@ final class GlobalItemTypeDictionary{
|
||||
use SingletonTrait;
|
||||
|
||||
private static function make() : self{
|
||||
$data = Utils::assumeNotFalse(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'required_item_list.json')), "Missing required resource file");
|
||||
$data = Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'required_item_list.json'));
|
||||
$table = json_decode($data, true);
|
||||
if(!is_array($table)){
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
|
@ -26,11 +26,11 @@ namespace pocketmine\network\mcpe\convert;
|
||||
use pocketmine\data\bedrock\LegacyItemIdToStringIdMap;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_key_exists;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function is_string;
|
||||
@ -67,7 +67,7 @@ final class ItemTranslator{
|
||||
private array $complexNetToCoreMapping = [];
|
||||
|
||||
private static function make() : self{
|
||||
$data = Utils::assumeNotFalse(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'r16_to_current_item_map.json')), "Missing required resource file");
|
||||
$data = Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'r16_to_current_item_map.json'));
|
||||
$json = json_decode($data, true);
|
||||
if(!is_array($json) || !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){
|
||||
throw new AssumptionFailedError("Invalid item table format");
|
||||
|
@ -30,10 +30,9 @@ use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -57,7 +56,7 @@ final class RuntimeBlockMapping{
|
||||
|
||||
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
|
||||
$stream = PacketSerializer::decoder(
|
||||
Utils::assumeNotFalse(file_get_contents($canonicalBlockStatesFile), "Missing required resource file"),
|
||||
Filesystem::fileGetContents($canonicalBlockStatesFile),
|
||||
0,
|
||||
new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())
|
||||
);
|
||||
@ -75,7 +74,7 @@ final class RuntimeBlockMapping{
|
||||
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
|
||||
$legacyStateMap = [];
|
||||
$legacyStateMapReader = PacketSerializer::decoder(
|
||||
Utils::assumeNotFalse(file_get_contents($r12ToCurrentBlockMapFile), "Missing required resource file"),
|
||||
Filesystem::fileGetContents($r12ToCurrentBlockMapFile),
|
||||
0,
|
||||
new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())
|
||||
);
|
||||
|
@ -23,19 +23,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\DeathInfoPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\RespawnPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerAction;
|
||||
use pocketmine\player\Player;
|
||||
use function array_map;
|
||||
|
||||
class DeathPacketHandler extends PacketHandler{
|
||||
public function __construct(
|
||||
private Player $player,
|
||||
private NetworkSession $session,
|
||||
private InventoryManager $inventoryManager
|
||||
private InventoryManager $inventoryManager,
|
||||
private Translatable|string $deathMessage
|
||||
){}
|
||||
|
||||
public function setUp() : void{
|
||||
@ -44,6 +48,22 @@ class DeathPacketHandler extends PacketHandler{
|
||||
RespawnPacket::SEARCHING_FOR_SPAWN,
|
||||
$this->player->getId()
|
||||
));
|
||||
|
||||
/** @var string[] $parameters */
|
||||
$parameters = [];
|
||||
if($this->deathMessage instanceof Translatable){
|
||||
$language = $this->player->getLanguage();
|
||||
if(!$this->player->getServer()->isLanguageForced()){
|
||||
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
|
||||
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $this->deathMessage->getParameters());
|
||||
$message = $language->translateString($this->deathMessage->getText(), $parameters, "pocketmine.");
|
||||
}else{
|
||||
$message = $language->translate($this->deathMessage);
|
||||
}
|
||||
}else{
|
||||
$message = $this->deathMessage;
|
||||
}
|
||||
$this->session->sendDataPacket(DeathInfoPacket::create($message, $parameters));
|
||||
}
|
||||
|
||||
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\handler;
|
||||
use pocketmine\block\BaseSign;
|
||||
use pocketmine\block\ItemFrame;
|
||||
use pocketmine\block\Lectern;
|
||||
use pocketmine\block\tile\Sign;
|
||||
use pocketmine\block\utils\SignText;
|
||||
use pocketmine\entity\animation\ConsumingItemAnimation;
|
||||
use pocketmine\entity\Attribute;
|
||||
@ -122,8 +123,8 @@ use function max;
|
||||
use function mb_strlen;
|
||||
use function microtime;
|
||||
use function sprintf;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
/**
|
||||
@ -661,7 +662,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
|
||||
|
||||
if($block instanceof BaseSign){
|
||||
if(($textBlobTag = $nbt->getTag("Text")) instanceof StringTag){
|
||||
if(($textBlobTag = $nbt->getTag(Sign::TAG_TEXT_BLOB)) instanceof StringTag){
|
||||
try{
|
||||
$text = SignText::fromBlob($textBlobTag->getValue());
|
||||
}catch(\InvalidArgumentException $e){
|
||||
@ -732,7 +733,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
public function handleCommandRequest(CommandRequestPacket $packet) : bool{
|
||||
if(strpos($packet->command, '/') === 0){
|
||||
if(str_starts_with($packet->command, '/')){
|
||||
$this->player->chat($packet->command);
|
||||
return true;
|
||||
}
|
||||
|
@ -35,10 +35,18 @@ final class DefaultPermissionNames{
|
||||
public const COMMAND_DIFFICULTY = "pocketmine.command.difficulty";
|
||||
public const COMMAND_DUMPMEMORY = "pocketmine.command.dumpmemory";
|
||||
public const COMMAND_EFFECT = "pocketmine.command.effect";
|
||||
public const COMMAND_EFFECT_OTHER = "pocketmine.command.effect.other";
|
||||
public const COMMAND_EFFECT_SELF = "pocketmine.command.effect.self";
|
||||
public const COMMAND_ENCHANT = "pocketmine.command.enchant";
|
||||
public const COMMAND_ENCHANT_OTHER = "pocketmine.command.enchant.other";
|
||||
public const COMMAND_ENCHANT_SELF = "pocketmine.command.enchant.self";
|
||||
public const COMMAND_GAMEMODE = "pocketmine.command.gamemode";
|
||||
public const COMMAND_GAMEMODE_OTHER = "pocketmine.command.gamemode.other";
|
||||
public const COMMAND_GAMEMODE_SELF = "pocketmine.command.gamemode.self";
|
||||
public const COMMAND_GC = "pocketmine.command.gc";
|
||||
public const COMMAND_GIVE = "pocketmine.command.give";
|
||||
public const COMMAND_GIVE_OTHER = "pocketmine.command.give.other";
|
||||
public const COMMAND_GIVE_SELF = "pocketmine.command.give.self";
|
||||
public const COMMAND_HELP = "pocketmine.command.help";
|
||||
public const COMMAND_KICK = "pocketmine.command.kick";
|
||||
public const COMMAND_KILL_OTHER = "pocketmine.command.kill.other";
|
||||
@ -56,9 +64,13 @@ final class DefaultPermissionNames{
|
||||
public const COMMAND_SEED = "pocketmine.command.seed";
|
||||
public const COMMAND_SETWORLDSPAWN = "pocketmine.command.setworldspawn";
|
||||
public const COMMAND_SPAWNPOINT = "pocketmine.command.spawnpoint";
|
||||
public const COMMAND_SPAWNPOINT_OTHER = "pocketmine.command.spawnpoint.other";
|
||||
public const COMMAND_SPAWNPOINT_SELF = "pocketmine.command.spawnpoint.self";
|
||||
public const COMMAND_STATUS = "pocketmine.command.status";
|
||||
public const COMMAND_STOP = "pocketmine.command.stop";
|
||||
public const COMMAND_TELEPORT = "pocketmine.command.teleport";
|
||||
public const COMMAND_TELEPORT_OTHER = "pocketmine.command.teleport.other";
|
||||
public const COMMAND_TELEPORT_SELF = "pocketmine.command.teleport.self";
|
||||
public const COMMAND_TELL = "pocketmine.command.tell";
|
||||
public const COMMAND_TIME_ADD = "pocketmine.command.time.add";
|
||||
public const COMMAND_TIME_QUERY = "pocketmine.command.time.query";
|
||||
@ -67,6 +79,8 @@ final class DefaultPermissionNames{
|
||||
public const COMMAND_TIME_STOP = "pocketmine.command.time.stop";
|
||||
public const COMMAND_TIMINGS = "pocketmine.command.timings";
|
||||
public const COMMAND_TITLE = "pocketmine.command.title";
|
||||
public const COMMAND_TITLE_OTHER = "pocketmine.command.title.other";
|
||||
public const COMMAND_TITLE_SELF = "pocketmine.command.title.self";
|
||||
public const COMMAND_TRANSFERSERVER = "pocketmine.command.transferserver";
|
||||
public const COMMAND_UNBAN_IP = "pocketmine.command.unban.ip";
|
||||
public const COMMAND_UNBAN_PLAYER = "pocketmine.command.unban.player";
|
||||
|
@ -23,10 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\permission\DefaultPermissionNames as Names;
|
||||
|
||||
abstract class DefaultPermissions{
|
||||
public const ROOT_CONSOLE = DefaultPermissionNames::GROUP_CONSOLE;
|
||||
public const ROOT_OPERATOR = DefaultPermissionNames::GROUP_OPERATOR;
|
||||
public const ROOT_USER = DefaultPermissionNames::GROUP_USER;
|
||||
public const ROOT_CONSOLE = Names::GROUP_CONSOLE;
|
||||
public const ROOT_OPERATOR = Names::GROUP_OPERATOR;
|
||||
public const ROOT_USER = Names::GROUP_USER;
|
||||
|
||||
/**
|
||||
* @param Permission[] $grantedBy
|
||||
@ -44,63 +46,95 @@ abstract class DefaultPermissions{
|
||||
return PermissionManager::getInstance()->getPermission($candidate->getName());
|
||||
}
|
||||
|
||||
private static function registerDeprecatedPermission(string $name) : Permission{
|
||||
$permission = new Permission($name, "Deprecated, kept for backwards compatibility only");
|
||||
PermissionManager::getInstance()->addPermission($permission);
|
||||
return $permission;
|
||||
}
|
||||
|
||||
public static function registerCorePermissions() : void{
|
||||
$consoleRoot = self::registerPermission(new Permission(self::ROOT_CONSOLE, "Grants all console permissions"));
|
||||
$operatorRoot = self::registerPermission(new Permission(self::ROOT_OPERATOR, "Grants all operator permissions"), [$consoleRoot]);
|
||||
$everyoneRoot = self::registerPermission(new Permission(self::ROOT_USER, "Grants all non-sensitive permissions that everyone gets by default"), [$operatorRoot]);
|
||||
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::BROADCAST_ADMIN, "Allows the user to receive administrative broadcasts"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::BROADCAST_USER, "Allows the user to receive user broadcasts"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_IP, "Allows the user to ban IP addresses"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_LIST, "Allows the user to list banned players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_BAN_PLAYER, "Allows the user to ban players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_OTHER, "Allows the user to clear inventory of other players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_CLEAR_SELF, "Allows the user to clear their own inventory"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DEFAULTGAMEMODE, "Allows the user to change the default gamemode"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DIFFICULTY, "Allows the user to change the game difficulty"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_DUMPMEMORY, "Allows the user to dump memory contents"), [$consoleRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_EFFECT, "Allows the user to give/take potion effects"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ENCHANT, "Allows the user to enchant items"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GAMEMODE, "Allows the user to change the gamemode of players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GC, "Allows the user to fire garbage collection tasks"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_GIVE, "Allows the user to give items to players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_HELP, "Allows the user to view the help menu"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KICK, "Allows the user to kick players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_OTHER, "Allows the user to kill other players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_KILL_SELF, "Allows the user to commit suicide"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_LIST, "Allows the user to list all online players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_ME, "Allows the user to perform a chat action"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_OP_GIVE, "Allows the user to give a player operator status"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_OP_TAKE, "Allows the user to take a player's operator status"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PARTICLE, "Allows the user to create particle effects"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_PLUGINS, "Allows the user to view the list of plugins"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_DISABLE, "Allows the user to disable automatic saving"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_ENABLE, "Allows the user to enable automatic saving"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAVE_PERFORM, "Allows the user to perform a manual save"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SAY, "Allows the user to talk as the console"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SEED, "Allows the user to view the seed of the world"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SETWORLDSPAWN, "Allows the user to change the world spawn"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_SPAWNPOINT, "Allows the user to change player's spawnpoint"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STATUS, "Allows the user to view the server performance"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_STOP, "Allows the user to stop the server"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELEPORT, "Allows the user to teleport players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TELL, "Allows the user to privately message another player"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_ADD, "Allows the user to fast-forward time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_QUERY, "Allows the user query the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_SET, "Allows the user to change the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_START, "Allows the user to restart the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIME_STOP, "Allows the user to stop the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TIMINGS, "Allows the user to record timings to analyse server performance"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TITLE, "Allows the user to send a title to the specified player"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_TRANSFERSERVER, "Allows the user to transfer self to another server"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_IP, "Allows the user to unban IP addresses"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER, "Allows the user to unban players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_VERSION, "Allows the user to view the version of the server"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ADD, "Allows the user to add a player to the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_DISABLE, "Allows the user to disable the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_ENABLE, "Allows the user to enable the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_LIST, "Allows the user to list all players on the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_RELOAD, "Allows the user to reload the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(DefaultPermissionNames::COMMAND_WHITELIST_REMOVE, "Allows the user to remove a player from the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::BROADCAST_ADMIN, "Allows the user to receive administrative broadcasts"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::BROADCAST_USER, "Allows the user to receive user broadcasts"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_BAN_IP, "Allows the user to ban IP addresses"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_BAN_LIST, "Allows the user to list banned players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_BAN_PLAYER, "Allows the user to ban players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CLEAR_OTHER, "Allows the user to clear inventory of other players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CLEAR_SELF, "Allows the user to clear their own inventory"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DEFAULTGAMEMODE, "Allows the user to change the default gamemode"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DIFFICULTY, "Allows the user to change the game difficulty"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DUMPMEMORY, "Allows the user to dump memory contents"), [$consoleRoot]);
|
||||
|
||||
$effectRoot = self::registerDeprecatedPermission(Names::COMMAND_EFFECT);
|
||||
self::registerPermission(new Permission(Names::COMMAND_EFFECT_OTHER, "Allows the user to modify effects of other players"), [$operatorRoot, $effectRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_EFFECT_SELF, "Allows the user to modify their own effects"), [$operatorRoot, $effectRoot]);
|
||||
|
||||
$enchantRoot = self::registerDeprecatedPermission(Names::COMMAND_ENCHANT);
|
||||
self::registerPermission(new Permission(Names::COMMAND_ENCHANT_OTHER, "Allows the user to enchant the held items of other players"), [$operatorRoot, $enchantRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_ENCHANT_SELF, "Allows the user to enchant their own held item"), [$operatorRoot, $enchantRoot]);
|
||||
|
||||
$gameModeRoot = self::registerDeprecatedPermission(Names::COMMAND_GAMEMODE);
|
||||
self::registerPermission(new Permission(Names::COMMAND_GAMEMODE_OTHER, "Allows the user to change the game mode of other players"), [$operatorRoot, $gameModeRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_GAMEMODE_SELF, "Allows the user to change their own game mode"), [$operatorRoot, $gameModeRoot]);
|
||||
|
||||
self::registerPermission(new Permission(Names::COMMAND_GC, "Allows the user to fire garbage collection tasks"), [$operatorRoot]);
|
||||
|
||||
$giveRoot = self::registerDeprecatedPermission(Names::COMMAND_GIVE);
|
||||
self::registerPermission(new Permission(Names::COMMAND_GIVE_OTHER, "Allows the user to give items to other players"), [$operatorRoot, $giveRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_GIVE_SELF, "Allows the user to give items to themselves"), [$operatorRoot, $giveRoot]);
|
||||
|
||||
self::registerPermission(new Permission(Names::COMMAND_HELP, "Allows the user to view the help menu"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_KICK, "Allows the user to kick players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_KILL_OTHER, "Allows the user to kill other players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_KILL_SELF, "Allows the user to commit suicide"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_LIST, "Allows the user to list all online players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_ME, "Allows the user to perform a chat action"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_OP_GIVE, "Allows the user to give a player operator status"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_OP_TAKE, "Allows the user to take a player's operator status"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_PARTICLE, "Allows the user to create particle effects"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_PLUGINS, "Allows the user to view the list of plugins"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SAVE_DISABLE, "Allows the user to disable automatic saving"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SAVE_ENABLE, "Allows the user to enable automatic saving"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SAVE_PERFORM, "Allows the user to perform a manual save"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SAY, "Allows the user to talk as the console"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SEED, "Allows the user to view the seed of the world"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SETWORLDSPAWN, "Allows the user to change the world spawn"), [$operatorRoot]);
|
||||
|
||||
$spawnpointRoot = self::registerDeprecatedPermission(Names::COMMAND_SPAWNPOINT);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SPAWNPOINT_OTHER, "Allows the user to change the respawn point of other players"), [$operatorRoot, $spawnpointRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_SPAWNPOINT_SELF, "Allows the user to change their own respawn point"), [$operatorRoot, $spawnpointRoot]);
|
||||
|
||||
self::registerPermission(new Permission(Names::COMMAND_STATUS, "Allows the user to view the server performance"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_STOP, "Allows the user to stop the server"), [$operatorRoot]);
|
||||
|
||||
$teleportRoot = self::registerDeprecatedPermission(Names::COMMAND_TELEPORT);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TELEPORT_OTHER, "Allows the user to teleport other players"), [$operatorRoot, $teleportRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TELEPORT_SELF, "Allows the user to teleport themselves"), [$operatorRoot, $teleportRoot]);
|
||||
|
||||
self::registerPermission(new Permission(Names::COMMAND_TELL, "Allows the user to privately message another player"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIME_ADD, "Allows the user to fast-forward time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIME_QUERY, "Allows the user query the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIME_SET, "Allows the user to change the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIME_START, "Allows the user to restart the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIME_STOP, "Allows the user to stop the time"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TIMINGS, "Allows the user to record timings to analyse server performance"), [$operatorRoot]);
|
||||
|
||||
$titleRoot = self::registerDeprecatedPermission(Names::COMMAND_TITLE);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TITLE_OTHER, "Allows the user to send a title to the specified player"), [$operatorRoot, $titleRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_TITLE_SELF, "Allows the user to send a title to themselves"), [$operatorRoot, $titleRoot]);
|
||||
|
||||
self::registerPermission(new Permission(Names::COMMAND_TRANSFERSERVER, "Allows the user to transfer self to another server"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_UNBAN_IP, "Allows the user to unban IP addresses"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_UNBAN_PLAYER, "Allows the user to unban players"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_VERSION, "Allows the user to view the version of the server"), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_ADD, "Allows the user to add a player to the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_DISABLE, "Allows the user to disable the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_ENABLE, "Allows the user to enable the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_LIST, "Allows the user to list all players on the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_RELOAD, "Allows the user to reload the server whitelist"), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_WHITELIST_REMOVE, "Allows the user to remove a player from the server whitelist"), [$operatorRoot]);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,10 @@ class PermissionParser{
|
||||
"false" => self::DEFAULT_FALSE,
|
||||
];
|
||||
|
||||
private const KEY_DEFAULT = "default";
|
||||
private const KEY_CHILDREN = "children";
|
||||
private const KEY_DESCRIPTION = "description";
|
||||
|
||||
/**
|
||||
* @param bool|string $value
|
||||
*
|
||||
@ -60,11 +64,7 @@ class PermissionParser{
|
||||
*/
|
||||
public static function defaultFromString($value) : string{
|
||||
if(is_bool($value)){
|
||||
if($value){
|
||||
return "true";
|
||||
}else{
|
||||
return "false";
|
||||
}
|
||||
return $value ? self::DEFAULT_TRUE : self::DEFAULT_FALSE;
|
||||
}
|
||||
$lower = strtolower($value);
|
||||
if(isset(self::DEFAULT_STRING_MAP[$lower])){
|
||||
@ -86,16 +86,16 @@ class PermissionParser{
|
||||
$result = [];
|
||||
foreach(Utils::stringifyKeys($data) as $name => $entry){
|
||||
$desc = null;
|
||||
if(isset($entry["default"])){
|
||||
$default = PermissionParser::defaultFromString($entry["default"]);
|
||||
if(isset($entry[self::KEY_DEFAULT])){
|
||||
$default = PermissionParser::defaultFromString($entry[self::KEY_DEFAULT]);
|
||||
}
|
||||
|
||||
if(isset($entry["children"])){
|
||||
if(isset($entry[self::KEY_CHILDREN])){
|
||||
throw new PermissionParserException("Nested permission declarations are no longer supported. Declare each permission separately.");
|
||||
}
|
||||
|
||||
if(isset($entry["description"])){
|
||||
$desc = $entry["description"];
|
||||
if(isset($entry[self::KEY_DESCRIPTION])){
|
||||
$desc = $entry[self::KEY_DESCRIPTION];
|
||||
}
|
||||
|
||||
$result[$default][] = new Permission($name, $desc);
|
||||
|
100
src/player/DatFilePlayerDataProvider.php
Normal file
100
src/player/DatFilePlayerDataProvider.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?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\player;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\nbt\BigEndianNbtSerializer;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_exists;
|
||||
use function rename;
|
||||
use function strtolower;
|
||||
use function zlib_decode;
|
||||
use function zlib_encode;
|
||||
use const ZLIB_ENCODING_GZIP;
|
||||
|
||||
/**
|
||||
* Stores player data in a single .dat file per player. Each file is gzipped big-endian NBT.
|
||||
*/
|
||||
final class DatFilePlayerDataProvider implements PlayerDataProvider{
|
||||
|
||||
public function __construct(
|
||||
private string $path
|
||||
){}
|
||||
|
||||
private function getPlayerDataPath(string $username) : string{
|
||||
return Path::join($this->path, strtolower($username) . '.dat');
|
||||
}
|
||||
|
||||
private function handleCorruptedPlayerData(string $name) : void{
|
||||
$path = $this->getPlayerDataPath($name);
|
||||
rename($path, $path . '.bak');
|
||||
}
|
||||
|
||||
public function hasData(string $name) : bool{
|
||||
return file_exists($this->getPlayerDataPath($name));
|
||||
}
|
||||
|
||||
public function loadData(string $name) : ?CompoundTag{
|
||||
$name = strtolower($name);
|
||||
$path = $this->getPlayerDataPath($name);
|
||||
|
||||
if(!file_exists($path)){
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
$contents = Filesystem::fileGetContents($path);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new PlayerDataLoadException("Failed to read player data file \"$path\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
try{
|
||||
$decompressed = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => zlib_decode($contents));
|
||||
}catch(\ErrorException $e){
|
||||
$this->handleCorruptedPlayerData($name);
|
||||
throw new PlayerDataLoadException("Failed to decompress raw player data for \"$name\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
try{
|
||||
return (new BigEndianNbtSerializer())->read($decompressed)->mustGetCompoundTag();
|
||||
}catch(NbtDataException $e){ //corrupt data
|
||||
$this->handleCorruptedPlayerData($name);
|
||||
throw new PlayerDataLoadException("Failed to decode NBT data for \"$name\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveData(string $name, CompoundTag $data) : void{
|
||||
$nbt = new BigEndianNbtSerializer();
|
||||
$contents = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot($data)), ZLIB_ENCODING_GZIP), "zlib_encode() failed unexpectedly");
|
||||
try{
|
||||
Filesystem::safeFilePutContents($this->getPlayerDataPath($name), $contents);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new PlayerDataSaveException("Failed to write player data file: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,11 +37,11 @@ class OfflinePlayer implements IPlayer{
|
||||
}
|
||||
|
||||
public function getFirstPlayed() : ?int{
|
||||
return ($this->namedtag !== null && ($firstPlayedTag = $this->namedtag->getTag("firstPlayed")) instanceof LongTag) ? $firstPlayedTag->getValue() : null;
|
||||
return ($this->namedtag !== null && ($firstPlayedTag = $this->namedtag->getTag(Player::TAG_FIRST_PLAYED)) instanceof LongTag) ? $firstPlayedTag->getValue() : null;
|
||||
}
|
||||
|
||||
public function getLastPlayed() : ?int{
|
||||
return ($this->namedtag !== null && ($lastPlayedTag = $this->namedtag->getTag("lastPlayed")) instanceof LongTag) ? $lastPlayedTag->getValue() : null;
|
||||
return ($this->namedtag !== null && ($lastPlayedTag = $this->namedtag->getTag(Player::TAG_LAST_PLAYED)) instanceof LongTag) ? $lastPlayedTag->getValue() : null;
|
||||
}
|
||||
|
||||
public function hasPlayedBefore() : bool{
|
||||
|
@ -131,7 +131,7 @@ use pocketmine\world\sound\Sound;
|
||||
use pocketmine\world\World;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function abs;
|
||||
use function array_map;
|
||||
use function array_filter;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function explode;
|
||||
@ -145,8 +145,8 @@ use function min;
|
||||
use function preg_match;
|
||||
use function spl_object_id;
|
||||
use function sqrt;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
use function trim;
|
||||
@ -175,6 +175,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
private const MAX_REACH_DISTANCE_SURVIVAL = 7;
|
||||
private const MAX_REACH_DISTANCE_ENTITY_INTERACTION = 8;
|
||||
|
||||
public const TAG_FIRST_PLAYED = "firstPlayed"; //TAG_Long
|
||||
public const TAG_LAST_PLAYED = "lastPlayed"; //TAG_Long
|
||||
private const TAG_GAME_MODE = "playerGameType"; //TAG_Int
|
||||
private const TAG_SPAWN_WORLD = "SpawnLevel"; //TAG_String
|
||||
private const TAG_SPAWN_X = "SpawnX"; //TAG_Int
|
||||
private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int
|
||||
private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int
|
||||
public const TAG_LEVEL = "Level"; //TAG_String
|
||||
public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
|
||||
|
||||
/**
|
||||
* Validates the given username.
|
||||
*/
|
||||
@ -343,10 +353,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
));
|
||||
|
||||
$this->firstPlayed = $nbt->getLong("firstPlayed", $now = (int) (microtime(true) * 1000));
|
||||
$this->lastPlayed = $nbt->getLong("lastPlayed", $now);
|
||||
$this->firstPlayed = $nbt->getLong(self::TAG_FIRST_PLAYED, $now = (int) (microtime(true) * 1000));
|
||||
$this->lastPlayed = $nbt->getLong(self::TAG_LAST_PLAYED, $now);
|
||||
|
||||
if(!$this->server->getForceGamemode() && ($gameModeTag = $nbt->getTag("playerGameType")) instanceof IntTag){
|
||||
if(!$this->server->getForceGamemode() && ($gameModeTag = $nbt->getTag(self::TAG_GAME_MODE)) instanceof IntTag){
|
||||
$this->internalSetGameMode(GameModeIdMap::getInstance()->fromId($gameModeTag->getValue()) ?? GameMode::SURVIVAL()); //TODO: bad hack here to avoid crashes on corrupted data
|
||||
}else{
|
||||
$this->internalSetGameMode($this->server->getGamemode());
|
||||
@ -358,8 +368,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->setNameTagAlwaysVisible();
|
||||
$this->setCanClimb();
|
||||
|
||||
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString("SpawnLevel", ""))) instanceof World){
|
||||
$this->spawnPosition = new Position($nbt->getInt("SpawnX"), $nbt->getInt("SpawnY"), $nbt->getInt("SpawnZ"), $world);
|
||||
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){
|
||||
$this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1425,7 +1435,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$message = TextFormat::clean($message, false);
|
||||
foreach(explode("\n", $message, $this->messageCounter + 1) as $messagePart){
|
||||
if(trim($messagePart) !== "" && strlen($messagePart) <= self::MAX_CHAT_BYTE_LENGTH && mb_strlen($messagePart, 'UTF-8') <= self::MAX_CHAT_CHAR_LENGTH && $this->messageCounter-- > 0){
|
||||
if(strpos($messagePart, './') === 0){
|
||||
if(str_starts_with($messagePart, './')){
|
||||
$messagePart = substr($messagePart, 1);
|
||||
}
|
||||
|
||||
@ -1436,7 +1446,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
break;
|
||||
}
|
||||
|
||||
if(strpos($ev->getMessage(), "/") === 0){
|
||||
if(str_starts_with($ev->getMessage(), "/")){
|
||||
Timings::$playerCommand->startTiming();
|
||||
$this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1));
|
||||
Timings::$playerCommand->stopTiming();
|
||||
@ -1818,7 +1828,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$ev->call();
|
||||
|
||||
$item = $this->inventory->getItemInHand();
|
||||
$oldItem = clone $item;
|
||||
if(!$ev->isCancelled()){
|
||||
if($item->onInteractEntity($this, $entity, $clickPos)){
|
||||
if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->inventory->getItemInHand())){
|
||||
if($item instanceof Durable && $item->isBroken()){
|
||||
$this->broadcastSound(new ItemBreakSound());
|
||||
}
|
||||
$this->inventory->setItemInHand($item);
|
||||
}
|
||||
}
|
||||
return $entity->onInteract($this, $clickPos);
|
||||
}
|
||||
return false;
|
||||
@ -1973,28 +1993,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* Sends a direct chat message to a player
|
||||
*/
|
||||
public function sendMessage(Translatable|string $message) : void{
|
||||
if($message instanceof Translatable){
|
||||
$this->sendTranslation($message->getText(), $message->getParameters());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getNetworkSession()->onRawChatMessage($message);
|
||||
$this->getNetworkSession()->onChatMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Player::sendMessage()} with a Translatable instead.
|
||||
* @param string[]|Translatable[] $parameters
|
||||
*/
|
||||
public function sendTranslation(string $message, array $parameters = []) : void{
|
||||
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
|
||||
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $this->getLanguage()->translate($p) : $p, $parameters);
|
||||
if(!$this->server->isLanguageForced()){
|
||||
foreach($parameters as $i => $p){
|
||||
$parameters[$i] = $this->getLanguage()->translateString($p, [], "pocketmine.");
|
||||
}
|
||||
$this->getNetworkSession()->onTranslatedChatMessage($this->getLanguage()->translateString($message, $parameters, "pocketmine."), $parameters);
|
||||
}else{
|
||||
$this->sendMessage($this->getLanguage()->translateString($message, $parameters));
|
||||
}
|
||||
$this->sendMessage(new Translatable($message, $parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2214,23 +2221,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
public function getSaveData() : CompoundTag{
|
||||
$nbt = $this->saveNBT();
|
||||
|
||||
$nbt->setString("LastKnownXUID", $this->xuid);
|
||||
$nbt->setString(self::TAG_LAST_KNOWN_XUID, $this->xuid);
|
||||
|
||||
if($this->location->isValid()){
|
||||
$nbt->setString("Level", $this->getWorld()->getFolderName());
|
||||
$nbt->setString(self::TAG_LEVEL, $this->getWorld()->getFolderName());
|
||||
}
|
||||
|
||||
if($this->hasValidCustomSpawn()){
|
||||
$spawn = $this->getSpawn();
|
||||
$nbt->setString("SpawnLevel", $spawn->getWorld()->getFolderName());
|
||||
$nbt->setInt("SpawnX", $spawn->getFloorX());
|
||||
$nbt->setInt("SpawnY", $spawn->getFloorY());
|
||||
$nbt->setInt("SpawnZ", $spawn->getFloorZ());
|
||||
$nbt->setString(self::TAG_SPAWN_WORLD, $spawn->getWorld()->getFolderName());
|
||||
$nbt->setInt(self::TAG_SPAWN_X, $spawn->getFloorX());
|
||||
$nbt->setInt(self::TAG_SPAWN_Y, $spawn->getFloorY());
|
||||
$nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ());
|
||||
}
|
||||
|
||||
$nbt->setInt("playerGameType", GameModeIdMap::getInstance()->toId($this->gamemode));
|
||||
$nbt->setLong("firstPlayed", $this->firstPlayed);
|
||||
$nbt->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
|
||||
$nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode));
|
||||
$nbt->setLong(self::TAG_FIRST_PLAYED, $this->firstPlayed);
|
||||
$nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000));
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
@ -2255,15 +2262,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->getWorld()->dropItem($this->location, $item);
|
||||
}
|
||||
|
||||
$clearInventory = fn(Inventory $inventory) => $inventory->setContents(array_filter($inventory->getContents(), fn(Item $item) => $item->keepOnDeath()));
|
||||
if($this->inventory !== null){
|
||||
$this->inventory->setHeldItemIndex(0);
|
||||
$this->inventory->clearAll();
|
||||
$clearInventory($this->inventory);
|
||||
}
|
||||
if($this->armorInventory !== null){
|
||||
$this->armorInventory->clearAll();
|
||||
$clearInventory($this->armorInventory);
|
||||
}
|
||||
if($this->offHandInventory !== null){
|
||||
$this->offHandInventory->clearAll();
|
||||
$clearInventory($this->offHandInventory);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2278,7 +2286,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$this->startDeathAnimation();
|
||||
|
||||
$this->getNetworkSession()->onServerDeath();
|
||||
$this->getNetworkSession()->onServerDeath($ev->getDeathMessage());
|
||||
}
|
||||
|
||||
protected function onDeathUpdate(int $tickDiff) : bool{
|
||||
@ -2303,16 +2311,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
$this->respawnLocked = true;
|
||||
|
||||
$this->logger->debug("Waiting for spawn terrain generation for respawn");
|
||||
$this->logger->debug("Waiting for safe respawn position to be located");
|
||||
$spawn = $this->getSpawn();
|
||||
$spawn->getWorld()->orderChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||
function() use ($spawn) : void{
|
||||
$spawn->getWorld()->requestSafeSpawn($spawn)->onCompletion(
|
||||
function(Position $safeSpawn) : void{
|
||||
if(!$this->isConnected()){
|
||||
return;
|
||||
}
|
||||
$this->logger->debug("Spawn terrain generation done, completing respawn");
|
||||
$spawn = $spawn->getWorld()->getSafeSpawn($spawn);
|
||||
$ev = new PlayerRespawnEvent($this, $spawn);
|
||||
$this->logger->debug("Respawn position located, completing respawn");
|
||||
$ev = new PlayerRespawnEvent($this, $safeSpawn);
|
||||
$ev->call();
|
||||
|
||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
|
||||
|
28
src/player/PlayerDataLoadException.php
Normal file
28
src/player/PlayerDataLoadException.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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\player;
|
||||
|
||||
final class PlayerDataLoadException extends \RuntimeException{
|
||||
|
||||
}
|
52
src/player/PlayerDataProvider.php
Normal file
52
src/player/PlayerDataProvider.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?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\player;
|
||||
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
|
||||
/**
|
||||
* Handles storage of player data. Implementations must treat player names in a case-insensitive manner.
|
||||
*/
|
||||
interface PlayerDataProvider{
|
||||
|
||||
/**
|
||||
* Returns whether there are any data associated with the given player name.
|
||||
*/
|
||||
public function hasData(string $name) : bool;
|
||||
|
||||
/**
|
||||
* Returns the data associated with the given player name, or null if there is no data.
|
||||
* TODO: we need an async version of this
|
||||
*
|
||||
* @throws PlayerDataLoadException
|
||||
*/
|
||||
public function loadData(string $name) : ?CompoundTag;
|
||||
|
||||
/**
|
||||
* Saves data for the give player name.
|
||||
*
|
||||
* @throws PlayerDataSaveException
|
||||
*/
|
||||
public function saveData(string $name, CompoundTag $data) : void;
|
||||
}
|
28
src/player/PlayerDataSaveException.php
Normal file
28
src/player/PlayerDataSaveException.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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\player;
|
||||
|
||||
final class PlayerDataSaveException extends \RuntimeException{
|
||||
|
||||
}
|
@ -24,8 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\plugin;
|
||||
|
||||
use function is_file;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function str_ends_with;
|
||||
|
||||
/**
|
||||
* Handles different types of plugins
|
||||
@ -36,8 +35,7 @@ class PharPluginLoader implements PluginLoader{
|
||||
){}
|
||||
|
||||
public function canLoadPlugin(string $path) : bool{
|
||||
$ext = ".phar";
|
||||
return is_file($path) && substr($path, -strlen($ext)) === $ext;
|
||||
return is_file($path) && str_ends_with($path, ".phar");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,8 +41,8 @@ use function file_exists;
|
||||
use function fopen;
|
||||
use function mkdir;
|
||||
use function rtrim;
|
||||
use function str_contains;
|
||||
use function stream_copy_to_stream;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
@ -145,7 +145,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
$pluginCmds = [];
|
||||
|
||||
foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
|
||||
if(strpos($key, ":") !== false){
|
||||
if(str_contains($key, ":")){
|
||||
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":")));
|
||||
continue;
|
||||
}
|
||||
@ -161,7 +161,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
|
||||
$aliasList = [];
|
||||
foreach($data->getAliases() as $alias){
|
||||
if(strpos($alias, ":") !== false){
|
||||
if(str_contains($alias, ":")){
|
||||
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":")));
|
||||
continue;
|
||||
}
|
||||
|
@ -37,6 +37,32 @@ use function stripos;
|
||||
use function yaml_parse;
|
||||
|
||||
class PluginDescription{
|
||||
private const KEY_NAME = "name";
|
||||
private const KEY_VERSION = "version";
|
||||
private const KEY_MAIN = "main";
|
||||
private const KEY_SRC_NAMESPACE_PREFIX = "src-namespace-prefix";
|
||||
private const KEY_API = "api";
|
||||
private const KEY_MCPE_PROTOCOL = "mcpe-protocol";
|
||||
private const KEY_OS = "os";
|
||||
private const KEY_DEPEND = "depend";
|
||||
private const KEY_SOFTDEPEND = "softdepend";
|
||||
private const KEY_LOADBEFORE = "loadbefore";
|
||||
private const KEY_EXTENSIONS = "extensions";
|
||||
private const KEY_WEBSITE = "website";
|
||||
private const KEY_DESCRIPTION = "description";
|
||||
private const KEY_LOGGER_PREFIX = "prefix";
|
||||
private const KEY_LOAD = "load";
|
||||
private const KEY_AUTHOR = "author";
|
||||
private const KEY_AUTHORS = "authors";
|
||||
private const KEY_PERMISSIONS = "permissions";
|
||||
|
||||
private const KEY_COMMANDS = "commands";
|
||||
private const KEY_COMMAND_PERMISSION = "permission";
|
||||
private const KEY_COMMAND_DESCRIPTION = self::KEY_DESCRIPTION;
|
||||
private const KEY_COMMAND_USAGE = "usage";
|
||||
private const KEY_COMMAND_ALIASES = "aliases";
|
||||
private const KEY_COMMAND_PERMISSION_MESSAGE = "permission-message";
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
* @phpstan-var array<string, mixed>
|
||||
@ -107,49 +133,49 @@ class PluginDescription{
|
||||
private function loadMap(array $plugin) : void{
|
||||
$this->map = $plugin;
|
||||
|
||||
$this->name = $plugin["name"];
|
||||
$this->name = $plugin[self::KEY_NAME];
|
||||
if(preg_match('/^[A-Za-z0-9 _.-]+$/', $this->name) === 0){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin name");
|
||||
}
|
||||
$this->name = str_replace(" ", "_", $this->name);
|
||||
$this->version = (string) $plugin["version"];
|
||||
$this->main = $plugin["main"];
|
||||
$this->version = (string) $plugin[self::KEY_VERSION];
|
||||
$this->main = $plugin[self::KEY_MAIN];
|
||||
if(stripos($this->main, "pocketmine\\") === 0){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin main, cannot start within the PocketMine namespace");
|
||||
}
|
||||
|
||||
$this->srcNamespacePrefix = $plugin["src-namespace-prefix"] ?? "";
|
||||
$this->srcNamespacePrefix = $plugin[self::KEY_SRC_NAMESPACE_PREFIX] ?? "";
|
||||
|
||||
$this->api = array_map("\strval", (array) ($plugin["api"] ?? []));
|
||||
$this->compatibleMcpeProtocols = array_map("\intval", (array) ($plugin["mcpe-protocol"] ?? []));
|
||||
$this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin["os"] ?? []));
|
||||
$this->api = array_map("\strval", (array) ($plugin[self::KEY_API] ?? []));
|
||||
$this->compatibleMcpeProtocols = array_map("\intval", (array) ($plugin[self::KEY_MCPE_PROTOCOL] ?? []));
|
||||
$this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin[self::KEY_OS] ?? []));
|
||||
|
||||
if(isset($plugin["commands"]) && is_array($plugin["commands"])){
|
||||
foreach($plugin["commands"] as $commandName => $commandData){
|
||||
if(isset($plugin[self::KEY_COMMANDS]) && is_array($plugin[self::KEY_COMMANDS])){
|
||||
foreach($plugin[self::KEY_COMMANDS] as $commandName => $commandData){
|
||||
if(!is_string($commandName)){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin commands, key must be the name of the command");
|
||||
}
|
||||
if(!is_array($commandData)){
|
||||
throw new PluginDescriptionParseException("Command $commandName has invalid properties");
|
||||
}
|
||||
if(!isset($commandData["permission"]) || !is_string($commandData["permission"])){
|
||||
if(!isset($commandData[self::KEY_COMMAND_PERMISSION]) || !is_string($commandData[self::KEY_COMMAND_PERMISSION])){
|
||||
throw new PluginDescriptionParseException("Command $commandName does not have a valid permission set");
|
||||
}
|
||||
$this->commands[$commandName] = new PluginDescriptionCommandEntry(
|
||||
$commandData["description"] ?? null,
|
||||
$commandData["usage"] ?? null,
|
||||
$commandData["aliases"] ?? [],
|
||||
$commandData["permission"],
|
||||
$commandData["permission-message"] ?? null
|
||||
$commandData[self::KEY_COMMAND_DESCRIPTION] ?? null,
|
||||
$commandData[self::KEY_COMMAND_USAGE] ?? null,
|
||||
$commandData[self::KEY_COMMAND_ALIASES] ?? [],
|
||||
$commandData[self::KEY_COMMAND_PERMISSION],
|
||||
$commandData[self::KEY_COMMAND_PERMISSION_MESSAGE] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($plugin["depend"])){
|
||||
$this->depend = (array) $plugin["depend"];
|
||||
if(isset($plugin[self::KEY_DEPEND])){
|
||||
$this->depend = (array) $plugin[self::KEY_DEPEND];
|
||||
}
|
||||
if(isset($plugin["extensions"])){
|
||||
$extensions = (array) $plugin["extensions"];
|
||||
if(isset($plugin[self::KEY_EXTENSIONS])){
|
||||
$extensions = (array) $plugin[self::KEY_EXTENSIONS];
|
||||
$isLinear = $extensions === array_values($extensions);
|
||||
foreach($extensions as $k => $v){
|
||||
if($isLinear){
|
||||
@ -160,20 +186,20 @@ class PluginDescription{
|
||||
}
|
||||
}
|
||||
|
||||
$this->softDepend = (array) ($plugin["softdepend"] ?? $this->softDepend);
|
||||
$this->softDepend = (array) ($plugin[self::KEY_SOFTDEPEND] ?? $this->softDepend);
|
||||
|
||||
$this->loadBefore = (array) ($plugin["loadbefore"] ?? $this->loadBefore);
|
||||
$this->loadBefore = (array) ($plugin[self::KEY_LOADBEFORE] ?? $this->loadBefore);
|
||||
|
||||
$this->website = (string) ($plugin["website"] ?? $this->website);
|
||||
$this->website = (string) ($plugin[self::KEY_WEBSITE] ?? $this->website);
|
||||
|
||||
$this->description = (string) ($plugin["description"] ?? $this->description);
|
||||
$this->description = (string) ($plugin[self::KEY_DESCRIPTION] ?? $this->description);
|
||||
|
||||
$this->prefix = (string) ($plugin["prefix"] ?? $this->prefix);
|
||||
$this->prefix = (string) ($plugin[self::KEY_LOGGER_PREFIX] ?? $this->prefix);
|
||||
|
||||
if(isset($plugin["load"])){
|
||||
$order = PluginEnableOrder::fromString($plugin["load"]);
|
||||
if(isset($plugin[self::KEY_LOAD])){
|
||||
$order = PluginEnableOrder::fromString($plugin[self::KEY_LOAD]);
|
||||
if($order === null){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin \"load\"");
|
||||
throw new PluginDescriptionParseException("Invalid Plugin \"" . self::KEY_LOAD . "\"");
|
||||
}
|
||||
$this->order = $order;
|
||||
}else{
|
||||
@ -181,24 +207,24 @@ class PluginDescription{
|
||||
}
|
||||
|
||||
$this->authors = [];
|
||||
if(isset($plugin["author"])){
|
||||
if(is_array($plugin["author"])){
|
||||
$this->authors = $plugin["author"];
|
||||
if(isset($plugin[self::KEY_AUTHOR])){
|
||||
if(is_array($plugin[self::KEY_AUTHOR])){
|
||||
$this->authors = $plugin[self::KEY_AUTHOR];
|
||||
}else{
|
||||
$this->authors[] = $plugin["author"];
|
||||
$this->authors[] = $plugin[self::KEY_AUTHOR];
|
||||
}
|
||||
}
|
||||
if(isset($plugin["authors"])){
|
||||
foreach($plugin["authors"] as $author){
|
||||
if(isset($plugin[self::KEY_AUTHORS])){
|
||||
foreach($plugin[self::KEY_AUTHORS] as $author){
|
||||
$this->authors[] = $author;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($plugin["permissions"])){
|
||||
if(isset($plugin[self::KEY_PERMISSIONS])){
|
||||
try{
|
||||
$this->permissions = PermissionParser::loadPermissions($plugin["permissions"]);
|
||||
$this->permissions = PermissionParser::loadPermissions($plugin[self::KEY_PERMISSIONS]);
|
||||
}catch(PermissionParserException $e){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin \"permissions\": " . $e->getMessage(), 0, $e);
|
||||
throw new PluginDescriptionParseException("Invalid Plugin \"" . self::KEY_PERMISSIONS . "\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ use function extension_loaded;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function phpversion;
|
||||
use function str_starts_with;
|
||||
use function stripos;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
@ -96,7 +97,7 @@ final class PluginLoadabilityChecker{
|
||||
}
|
||||
foreach(["<=", "le", "<>", "!=", "ne", "<", "lt", "==", "=", "eq", ">=", "ge", ">", "gt"] as $comparator){
|
||||
// warning: the > character should be quoted in YAML
|
||||
if(substr($constr, 0, strlen($comparator)) === $comparator){
|
||||
if(str_starts_with($constr, $comparator)){
|
||||
$version = substr($constr, strlen($comparator));
|
||||
if(!version_compare($gotVersion, $version, $comparator)){
|
||||
return KnownTranslationFactory::pocketmine_plugin_incompatibleExtensionVersion(extensionName: $extensionName, extensionVersion: $gotVersion, pluginRequirement: $constr);
|
||||
|
@ -62,7 +62,7 @@ use function mkdir;
|
||||
use function realpath;
|
||||
use function shuffle;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@ -296,7 +296,7 @@ class PluginManager{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strpos($name, " ") !== false){
|
||||
if(str_contains($name, " ")){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_spacesDiscouraged($name)));
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,8 @@ use function count;
|
||||
use function file;
|
||||
use function implode;
|
||||
use function is_file;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
use function str_contains;
|
||||
use function str_ends_with;
|
||||
use const FILE_IGNORE_NEW_LINES;
|
||||
use const FILE_SKIP_EMPTY_LINES;
|
||||
|
||||
@ -41,8 +40,7 @@ use const FILE_SKIP_EMPTY_LINES;
|
||||
class ScriptPluginLoader implements PluginLoader{
|
||||
|
||||
public function canLoadPlugin(string $path) : bool{
|
||||
$ext = ".php";
|
||||
return is_file($path) && substr($path, -strlen($ext)) === $ext;
|
||||
return is_file($path) && str_ends_with($path, ".php");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +64,7 @@ class ScriptPluginLoader implements PluginLoader{
|
||||
$docCommentLines = [];
|
||||
foreach($content as $line){
|
||||
if(!$insideHeader){
|
||||
if(strpos($line, "/**") !== false){
|
||||
if(str_contains($line, "/**")){
|
||||
$insideHeader = true;
|
||||
}else{
|
||||
continue;
|
||||
@ -75,7 +73,7 @@ class ScriptPluginLoader implements PluginLoader{
|
||||
|
||||
$docCommentLines[] = $line;
|
||||
|
||||
if(strpos($line, "*/") !== false){
|
||||
if(str_contains($line, "*/")){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\resourcepacks;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_keys;
|
||||
use function copy;
|
||||
use function count;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function gettype;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
@ -38,6 +37,8 @@ use function is_float;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function mkdir;
|
||||
use function rtrim;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
@ -93,41 +94,24 @@ class ResourcePackManager{
|
||||
}
|
||||
$pack = (string) $pack;
|
||||
try{
|
||||
$packPath = Path::join($this->path, $pack);
|
||||
if(!file_exists($packPath)){
|
||||
throw new ResourcePackException("File or directory not found");
|
||||
}
|
||||
if(is_dir($packPath)){
|
||||
throw new ResourcePackException("Directory resource packs are unsupported");
|
||||
}
|
||||
$newPack = $this->loadPackFromPath(Path::join($this->path, $pack));
|
||||
|
||||
$newPack = null;
|
||||
//Detect the type of resource pack.
|
||||
$info = new \SplFileInfo($packPath);
|
||||
switch($info->getExtension()){
|
||||
case "zip":
|
||||
case "mcpack":
|
||||
$newPack = new ZippedResourcePack($packPath);
|
||||
break;
|
||||
}
|
||||
$this->resourcePacks[] = $newPack;
|
||||
$index = strtolower($newPack->getPackId());
|
||||
$this->uuidList[$index] = $newPack;
|
||||
|
||||
if($newPack instanceof ResourcePack){
|
||||
$this->resourcePacks[] = $newPack;
|
||||
$index = strtolower($newPack->getPackId());
|
||||
$this->uuidList[$index] = $newPack;
|
||||
|
||||
$keyPath = Path::join($this->path, $pack . ".key");
|
||||
if(file_exists($keyPath)){
|
||||
try{
|
||||
$this->encryptionKeys[$index] = ErrorToExceptionHandler::trapAndRemoveFalse(
|
||||
fn() => file_get_contents($keyPath)
|
||||
);
|
||||
}catch(\ErrorException $e){
|
||||
throw new ResourcePackException("Could not read encryption key file: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
$keyPath = Path::join($this->path, $pack . ".key");
|
||||
if(file_exists($keyPath)){
|
||||
try{
|
||||
$key = Filesystem::fileGetContents($keyPath);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new ResourcePackException("Could not read encryption key file: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}else{
|
||||
throw new ResourcePackException("Format not recognized");
|
||||
$key = rtrim($key, "\r\n");
|
||||
if(strlen($key) !== 32){
|
||||
throw new ResourcePackException("Invalid encryption key length, must be exactly 32 bytes");
|
||||
}
|
||||
$this->encryptionKeys[$index] = $key;
|
||||
}
|
||||
}catch(ResourcePackException $e){
|
||||
$logger->critical("Could not load resource pack \"$pack\": " . $e->getMessage());
|
||||
@ -137,6 +121,25 @@ class ResourcePackManager{
|
||||
$logger->debug("Successfully loaded " . count($this->resourcePacks) . " resource packs");
|
||||
}
|
||||
|
||||
private function loadPackFromPath(string $packPath) : ResourcePack{
|
||||
if(!file_exists($packPath)){
|
||||
throw new ResourcePackException("File or directory not found");
|
||||
}
|
||||
if(is_dir($packPath)){
|
||||
throw new ResourcePackException("Directory resource packs are unsupported");
|
||||
}
|
||||
|
||||
//Detect the type of resource pack.
|
||||
$info = new \SplFileInfo($packPath);
|
||||
switch($info->getExtension()){
|
||||
case "zip":
|
||||
case "mcpack":
|
||||
return new ZippedResourcePack($packPath);
|
||||
}
|
||||
|
||||
throw new ResourcePackException("Format not recognized");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory which resource packs are loaded from.
|
||||
*/
|
||||
@ -159,6 +162,29 @@ class ResourcePackManager{
|
||||
return $this->resourcePacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resource packs to use. Packs earliest in the list will appear at the top of the stack (maximum
|
||||
* priority), and later ones will appear below (lower priority), in the same manner as the Bedrock resource packs
|
||||
* screen in-game.
|
||||
*
|
||||
* @param ResourcePack[] $resourceStack
|
||||
* @phpstan-param list<ResourcePack> $resourceStack
|
||||
*/
|
||||
public function setResourceStack(array $resourceStack) : void{
|
||||
$uuidList = [];
|
||||
$resourcePacks = [];
|
||||
foreach($resourceStack as $pack){
|
||||
$uuid = strtolower($pack->getPackId());
|
||||
if(isset($uuidList[$uuid])){
|
||||
throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)");
|
||||
}
|
||||
$uuidList[$uuid] = $pack;
|
||||
$resourcePacks[] = $pack;
|
||||
}
|
||||
$this->resourcePacks = $resourcePacks;
|
||||
$this->uuidList = $uuidList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource pack matching the specified UUID string, or null if the ID was not recognized.
|
||||
*/
|
||||
@ -180,4 +206,23 @@ class ResourcePackManager{
|
||||
public function getPackEncryptionKey(string $id) : ?string{
|
||||
return $this->encryptionKeys[strtolower($id)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption key to use for decrypting the specified resource pack. The pack will **NOT** be decrypted by
|
||||
* PocketMine-MP; the key is simply passed to the client to allow it to decrypt the pack after downloading it.
|
||||
*/
|
||||
public function setPackEncryptionKey(string $id, ?string $key) : void{
|
||||
$id = strtolower($id);
|
||||
if($key === null){
|
||||
//allow deprovisioning keys for resource packs that have been removed
|
||||
unset($this->encryptionKeys[$id]);
|
||||
}elseif(isset($this->uuidList[$id])){
|
||||
if(strlen($key) !== 32){
|
||||
throw new \InvalidArgumentException("Encryption key must be exactly 32 bytes long");
|
||||
}
|
||||
$this->encryptionKeys[$id] = $key;
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Unknown pack ID $id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\Server;
|
||||
use function round;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -54,8 +55,8 @@ final class TimingsRecord{
|
||||
public static function tick(bool $measure = true) : void{
|
||||
if($measure){
|
||||
foreach(self::$records as $record){
|
||||
if($record->curTickTotal > 50000000){
|
||||
$record->violations += (int) round($record->curTickTotal / 50000000);
|
||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||
$record->violations += (int) round($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||
}
|
||||
$record->curTickTotal = 0;
|
||||
$record->curCount = 0;
|
||||
|
@ -33,7 +33,6 @@ use function count;
|
||||
use function date;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
@ -162,10 +161,7 @@ class Config{
|
||||
$this->config = $default;
|
||||
$this->save();
|
||||
}else{
|
||||
$content = file_get_contents($this->file);
|
||||
if($content === false){
|
||||
throw new \RuntimeException("Unable to load config file");
|
||||
}
|
||||
$content = Filesystem::fileGetContents($this->file);
|
||||
switch($this->type){
|
||||
case Config::PROPERTIES:
|
||||
$config = self::parseProperties($content);
|
||||
|
@ -30,6 +30,7 @@ use function dirname;
|
||||
use function fclose;
|
||||
use function fflush;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function flock;
|
||||
use function fopen;
|
||||
@ -47,9 +48,9 @@ use function rmdir;
|
||||
use function rtrim;
|
||||
use function scandir;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
use function stream_get_contents;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function uksort;
|
||||
use function unlink;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
@ -171,7 +172,7 @@ final class Filesystem{
|
||||
//this should probably never have integer keys, but it's safer than making PHPStan ignore it
|
||||
foreach(Utils::stringifyKeys(self::$cleanedPaths) as $cleanPath => $replacement){
|
||||
$cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/");
|
||||
if(strpos($result, $cleanPath) === 0){
|
||||
if(str_starts_with($result, $cleanPath)){
|
||||
$result = ltrim(str_replace($cleanPath, $replacement, $result), "/");
|
||||
}
|
||||
}
|
||||
@ -296,4 +297,21 @@ final class Filesystem{
|
||||
@unlink($temporaryFileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around file_get_contents() which throws an exception instead of generating E_* errors.
|
||||
*
|
||||
* @phpstan-param resource|null $context
|
||||
* @phpstan-param 0|positive-int $offset
|
||||
* @phpstan-param 0|positive-int|null $length
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function fileGetContents(string $fileName, bool $useIncludePath = false, $context = null, int $offset = 0, ?int $length = null) : string{
|
||||
try{
|
||||
return ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents($fileName, $useIncludePath, $context, $offset, $length));
|
||||
}catch(\ErrorException $e){
|
||||
throw new \RuntimeException("Failed to read file $fileName: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ use function posix_kill;
|
||||
use function preg_match;
|
||||
use function proc_close;
|
||||
use function proc_open;
|
||||
use function str_starts_with;
|
||||
use function stream_get_contents;
|
||||
use function strpos;
|
||||
use function trim;
|
||||
|
||||
final class Process{
|
||||
@ -99,9 +99,9 @@ final class Process{
|
||||
if($mappings === false) throw new AssumptionFailedError("/proc/self/maps should always be accessible");
|
||||
foreach($mappings as $line){
|
||||
if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){
|
||||
if(strpos($matches[3], "heap") === 0){
|
||||
if(str_starts_with($matches[3], "heap")){
|
||||
$heap += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
|
||||
}elseif(strpos($matches[3], "stack") === 0){
|
||||
}elseif(str_starts_with($matches[3], "stack")){
|
||||
$stack += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,9 @@ use function json_decode;
|
||||
use function parse_ini_file;
|
||||
use function preg_match;
|
||||
use function readlink;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function str_starts_with;
|
||||
use function substr;
|
||||
use function timezone_abbreviations_list;
|
||||
use function timezone_name_from_abbr;
|
||||
@ -61,7 +62,7 @@ abstract class Timezone{
|
||||
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
|
||||
* an incorrect timezone abbreviation in php.ini apparently.
|
||||
*/
|
||||
if(strpos($timezone, "/") === false){
|
||||
if(!str_contains($timezone, "/")){
|
||||
$default_timezone = timezone_name_from_abbr($timezone);
|
||||
if($default_timezone !== false){
|
||||
ini_set("date.timezone", $default_timezone);
|
||||
@ -165,7 +166,7 @@ abstract class Timezone{
|
||||
return self::parseOffset($offset);
|
||||
case Utils::OS_MACOS:
|
||||
$filename = @readlink('/etc/localtime');
|
||||
if($filename !== false && strpos($filename, '/usr/share/zoneinfo/') === 0){
|
||||
if($filename !== false && str_starts_with($filename, '/usr/share/zoneinfo/')){
|
||||
$timezone = substr($filename, 20);
|
||||
return trim($timezone);
|
||||
}
|
||||
@ -183,11 +184,11 @@ abstract class Timezone{
|
||||
*/
|
||||
private static function parseOffset($offset){
|
||||
//Make signed offsets unsigned for date_parse
|
||||
if(strpos($offset, '-') !== false){
|
||||
if(str_starts_with($offset, '-')){
|
||||
$negative_offset = true;
|
||||
$offset = str_replace('-', '', $offset);
|
||||
}else{
|
||||
if(strpos($offset, '+') !== false){
|
||||
if(str_starts_with($offset, '+')){
|
||||
$negative_offset = false;
|
||||
$offset = str_replace('+', '', $offset);
|
||||
}else{
|
||||
|
@ -79,11 +79,12 @@ use function preg_match_all;
|
||||
use function preg_replace;
|
||||
use function shell_exec;
|
||||
use function spl_object_id;
|
||||
use function str_ends_with;
|
||||
use function str_pad;
|
||||
use function str_split;
|
||||
use function str_starts_with;
|
||||
use function stripos;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
use function sys_get_temp_dir;
|
||||
use function trim;
|
||||
@ -119,7 +120,7 @@ final class Utils{
|
||||
*/
|
||||
public static function getNiceClosureName(\Closure $closure) : string{
|
||||
$func = new \ReflectionFunction($closure);
|
||||
if(substr($func->getName(), -strlen('{closure}')) !== '{closure}'){
|
||||
if(!str_ends_with($func->getName(), '{closure}')){
|
||||
//closure wraps a named function, can be done with reflection or fromCallable()
|
||||
//isClosure() is useless here because it just tells us if $func is reflecting a Closure object
|
||||
|
||||
@ -273,7 +274,7 @@ final class Utils{
|
||||
if(self::$os === null || $recalculate){
|
||||
$uname = php_uname("s");
|
||||
if(stripos($uname, "Darwin") !== false){
|
||||
if(strpos(php_uname("m"), "iP") === 0){
|
||||
if(str_starts_with(php_uname("m"), "iP")){
|
||||
self::$os = self::OS_IOS;
|
||||
}else{
|
||||
self::$os = self::OS_MACOS;
|
||||
|
@ -50,7 +50,9 @@ use pocketmine\event\world\ChunkLoadEvent;
|
||||
use pocketmine\event\world\ChunkPopulateEvent;
|
||||
use pocketmine\event\world\ChunkUnloadEvent;
|
||||
use pocketmine\event\world\SpawnChangeEvent;
|
||||
use pocketmine\event\world\WorldParticleEvent;
|
||||
use pocketmine\event\world\WorldSaveEvent;
|
||||
use pocketmine\event\world\WorldSoundEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemUseResult;
|
||||
use pocketmine\item\LegacyStringToItemParser;
|
||||
@ -666,9 +668,19 @@ class World implements ChunkManager{
|
||||
* @param Player[]|null $players
|
||||
*/
|
||||
public function addSound(Vector3 $pos, Sound $sound, ?array $players = null) : void{
|
||||
$pk = $sound->encode($pos);
|
||||
$players ??= $this->getViewersForPosition($pos);
|
||||
$ev = new WorldSoundEvent($this, $sound, $pos, $players);
|
||||
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
||||
$pk = $ev->getSound()->encode($pos);
|
||||
$players = $ev->getRecipients();
|
||||
if(count($pk) > 0){
|
||||
if($players === null){
|
||||
if($players === $this->getViewersForPosition($pos)){
|
||||
foreach($pk as $e){
|
||||
$this->broadcastPacketToViewers($pos, $e);
|
||||
}
|
||||
@ -682,14 +694,24 @@ class World implements ChunkManager{
|
||||
* @param Player[]|null $players
|
||||
*/
|
||||
public function addParticle(Vector3 $pos, Particle $particle, ?array $players = null) : void{
|
||||
$pk = $particle->encode($pos);
|
||||
$players ??= $this->getViewersForPosition($pos);
|
||||
$ev = new WorldParticleEvent($this, $particle, $pos, $players);
|
||||
|
||||
$ev->call();
|
||||
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
||||
$pk = $ev->getParticle()->encode($pos);
|
||||
$players = $ev->getRecipients();
|
||||
if(count($pk) > 0){
|
||||
if($players === null){
|
||||
if($players === $this->getViewersForPosition($pos)){
|
||||
foreach($pk as $e){
|
||||
$this->broadcastPacketToViewers($pos, $e);
|
||||
}
|
||||
}else{
|
||||
$this->server->broadcastPackets($this->filterViewersForPosition($pos, $players), $pk);
|
||||
$this->server->broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1130,6 +1152,8 @@ class World implements ChunkManager{
|
||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
||||
$chunkTickList = [];
|
||||
|
||||
$chunkTickableCache = [];
|
||||
|
||||
$centerChunks = [];
|
||||
|
||||
$selector = new ChunkSelector();
|
||||
@ -1149,7 +1173,7 @@ class World implements ChunkManager{
|
||||
$centerChunkZ
|
||||
) as $hash){
|
||||
World::getXZ($hash, $chunkX, $chunkZ);
|
||||
if(!isset($chunkTickList[$hash]) && isset($this->chunks[$hash]) && $this->isChunkTickable($chunkX, $chunkZ)){
|
||||
if(!isset($chunkTickList[$hash]) && isset($this->chunks[$hash]) && $this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
|
||||
$chunkTickList[$hash] = true;
|
||||
}
|
||||
}
|
||||
@ -1164,14 +1188,29 @@ class World implements ChunkManager{
|
||||
}
|
||||
}
|
||||
|
||||
private function isChunkTickable(int $chunkX, int $chunkZ) : bool{
|
||||
/**
|
||||
* @param bool[] &$cache
|
||||
*
|
||||
* @phpstan-param array<int, bool> $cache
|
||||
* @phpstan-param-out array<int, bool> $cache
|
||||
*/
|
||||
private function isChunkTickable(int $chunkX, int $chunkZ, array &$cache) : bool{
|
||||
for($cx = -1; $cx <= 1; ++$cx){
|
||||
for($cz = -1; $cz <= 1; ++$cz){
|
||||
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
|
||||
if(isset($cache[$chunkHash])){
|
||||
if(!$cache[$chunkHash]){
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if($this->isChunkLocked($chunkX + $cx, $chunkZ + $cz)){
|
||||
$cache[$chunkHash] = false;
|
||||
return false;
|
||||
}
|
||||
$adjacentChunk = $this->getChunk($chunkX + $cx, $chunkZ + $cz);
|
||||
if($adjacentChunk === null || !$adjacentChunk->isPopulated()){
|
||||
$cache[$chunkHash] = false;
|
||||
return false;
|
||||
}
|
||||
$lightPopulatedState = $adjacentChunk->isLightPopulated();
|
||||
@ -1179,8 +1218,11 @@ class World implements ChunkManager{
|
||||
if($lightPopulatedState === false){
|
||||
$this->orderLightPopulation($chunkX + $cx, $chunkZ + $cz);
|
||||
}
|
||||
$cache[$chunkHash] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$cache[$chunkHash] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1749,10 +1791,7 @@ class World implements ChunkManager{
|
||||
|
||||
if($update){
|
||||
$this->updateAllLight($x, $y, $z);
|
||||
$this->tryAddToNeighbourUpdateQueue($pos);
|
||||
foreach($pos->sides() as $side){
|
||||
$this->tryAddToNeighbourUpdateQueue($side);
|
||||
}
|
||||
$this->notifyNeighbourBlockUpdate($pos);
|
||||
}
|
||||
|
||||
$this->timings->setBlock->stopTiming();
|
||||
@ -2797,6 +2836,36 @@ class World implements ChunkManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a safe spawn position near the given position, or near the world's spawn position if not provided.
|
||||
* Terrain near the position will be loaded or generated as needed.
|
||||
*
|
||||
* @return Promise Resolved to a Position object, or rejected if the world is unloaded.
|
||||
* @phpstan-return Promise<Position>
|
||||
*/
|
||||
public function requestSafeSpawn(?Vector3 $spawn = null) : Promise{
|
||||
$resolver = new PromiseResolver();
|
||||
$spawn ??= $this->getSpawnLocation();
|
||||
/*
|
||||
* TODO: this relies on the assumption that getSafeSpawn() will only alter the Y coordinate of the provided
|
||||
* position, which is currently OK, but might be a problem in the future.
|
||||
*/
|
||||
$this->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||
function() use ($spawn, $resolver) : void{
|
||||
$spawn = $this->getSafeSpawn($spawn);
|
||||
$resolver->resolve($spawn);
|
||||
},
|
||||
function() use ($resolver) : void{
|
||||
$resolver->reject();
|
||||
}
|
||||
);
|
||||
|
||||
return $resolver->getPromise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a safe spawn position near the given position, or near the world's spawn position if not provided.
|
||||
* This function will throw an exception if the terrain is not already generated in advance.
|
||||
*
|
||||
* @throws WorldException if the terrain is not generated
|
||||
*/
|
||||
public function getSafeSpawn(?Vector3 $spawn = null) : Position{
|
||||
|
@ -55,12 +55,14 @@ use function strval;
|
||||
use function trim;
|
||||
|
||||
class WorldManager{
|
||||
public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND;
|
||||
|
||||
/** @var World[] */
|
||||
private array $worlds = [];
|
||||
private ?World $defaultWorld = null;
|
||||
|
||||
private bool $autoSave = true;
|
||||
private int $autoSaveTicks = 6000;
|
||||
private int $autoSaveTicks = self::TICKS_PER_AUTOSAVE;
|
||||
private int $autoSaveTicker = 0;
|
||||
|
||||
public function __construct(
|
||||
@ -348,8 +350,8 @@ class WorldManager{
|
||||
$world->doTick($currentTick);
|
||||
$tickMs = (microtime(true) - $worldTime) * 1000;
|
||||
$world->tickRateTime = $tickMs;
|
||||
if($tickMs >= 50){
|
||||
$world->getLogger()->debug(sprintf("Tick took too long: %gms (%g ticks)", $tickMs, round($tickMs / 50, 2)));
|
||||
if($tickMs >= Server::TARGET_SECONDS_PER_TICK * 1000){
|
||||
$world->getLogger()->debug(sprintf("Tick took too long: %gms (%g ticks)", $tickMs, round($tickMs / (Server::TARGET_SECONDS_PER_TICK * 1000), 2)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ use pocketmine\world\generator\GeneratorManager;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\world\WorldCreationOptions;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
@ -111,9 +110,10 @@ class BedrockWorldData extends BaseNbtWorldData{
|
||||
}
|
||||
|
||||
protected function load() : CompoundTag{
|
||||
$rawLevelData = @file_get_contents($this->dataPath);
|
||||
if($rawLevelData === false){
|
||||
throw new CorruptedWorldException("Failed to read level.dat (permission denied or doesn't exist)");
|
||||
try{
|
||||
$rawLevelData = Filesystem::fileGetContents($this->dataPath);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new CorruptedWorldException($e->getMessage(), 0, $e);
|
||||
}
|
||||
if(strlen($rawLevelData) <= 8){
|
||||
throw new CorruptedWorldException("Truncated level.dat");
|
||||
|
@ -37,7 +37,6 @@ use pocketmine\world\World;
|
||||
use pocketmine\world\WorldCreationOptions;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function ceil;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function microtime;
|
||||
use function zlib_decode;
|
||||
@ -75,9 +74,10 @@ class JavaWorldData extends BaseNbtWorldData{
|
||||
}
|
||||
|
||||
protected function load() : CompoundTag{
|
||||
$rawLevelData = @file_get_contents($this->dataPath);
|
||||
if($rawLevelData === false){
|
||||
throw new CorruptedWorldException("Failed to read level.dat (permission denied or doesn't exist)");
|
||||
try{
|
||||
$rawLevelData = Filesystem::fileGetContents($this->dataPath);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new CorruptedWorldException($e->getMessage(), 0, $e);
|
||||
}
|
||||
$nbt = new BigEndianNbtSerializer();
|
||||
$decompressed = @zlib_decode($rawLevelData);
|
||||
|
@ -617,7 +617,7 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getLanguage\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#"
|
||||
count: 1
|
||||
count: 2
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
|
Loading…
x
Reference in New Issue
Block a user