mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-19 23:37:45 +00:00
Merge branch 'stable' into item-stack-request
This commit is contained in:
@@ -286,7 +286,7 @@ class MemoryManager{
|
||||
/**
|
||||
* Static memory dumper accessible from any thread.
|
||||
*
|
||||
* @param mixed $startingObject
|
||||
* @param mixed $startingObject
|
||||
*/
|
||||
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
|
||||
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
|
||||
@@ -398,7 +398,7 @@ class MemoryManager{
|
||||
|
||||
do{
|
||||
$continue = false;
|
||||
foreach($objects as $hash => $object){
|
||||
foreach(Utils::stringifyKeys($objects) as $hash => $object){
|
||||
if(!is_object($object)){
|
||||
continue;
|
||||
}
|
||||
@@ -480,9 +480,14 @@ class MemoryManager{
|
||||
|
||||
/**
|
||||
* @param mixed $from
|
||||
* @param object[] $objects reference parameter
|
||||
* @param object[] $objects reference parameter
|
||||
* @param int[] $refCounts reference parameter
|
||||
*
|
||||
* @phpstan-param array<string, object> $objects
|
||||
* @phpstan-param array<string, int> $refCounts
|
||||
* @phpstan-param-out array<string, object> $objects
|
||||
* @phpstan-param-out array<string, int> $refCounts
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function continueDump($from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||
|
@@ -39,11 +39,14 @@ namespace pocketmine {
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
use function getcwd;
|
||||
use function is_dir;
|
||||
use function mkdir;
|
||||
use function phpversion;
|
||||
use function preg_match;
|
||||
use function preg_quote;
|
||||
use function realpath;
|
||||
use function version_compare;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
require_once __DIR__ . '/VersionInfo.php';
|
||||
|
||||
@@ -201,6 +204,22 @@ JIT_WARNING
|
||||
ini_set('assert.exception', '1');
|
||||
}
|
||||
|
||||
function getopt_string(string $opt) : ?string{
|
||||
$opts = getopt("", ["$opt:"]);
|
||||
if(isset($opts[$opt])){
|
||||
if(is_string($opts[$opt])){
|
||||
return $opts[$opt];
|
||||
}
|
||||
if(is_array($opts[$opt])){
|
||||
critical_error("Cannot specify --$opt multiple times");
|
||||
}else{
|
||||
critical_error("Missing value for --$opt");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@@ -252,27 +271,42 @@ JIT_WARNING
|
||||
|
||||
ErrorToExceptionHandler::set();
|
||||
|
||||
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
|
||||
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
|
||||
$dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR;
|
||||
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR;
|
||||
$dataPath = getopt_string("data") ?? $cwd;
|
||||
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
|
||||
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
||||
|
||||
if(!file_exists($dataPath)){
|
||||
mkdir($dataPath, 0777, true);
|
||||
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
|
||||
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
|
||||
exit(1);
|
||||
}
|
||||
//this has to be done after we're sure the data path exists
|
||||
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$lockFilePath = Path::join($dataPath, 'server.lock');
|
||||
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
|
||||
try{
|
||||
$pid = Filesystem::createLockFile($lockFilePath);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
critical_error($e->getMessage());
|
||||
critical_error("Please ensure that there is enough space on the disk and that the current user has read/write permissions to the selected data directory $dataPath.");
|
||||
exit(1);
|
||||
}
|
||||
if($pid !== null){
|
||||
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
|
||||
critical_error("Please stop the other server first before running a new one.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
|
||||
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
|
||||
exit(1);
|
||||
}
|
||||
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
|
||||
|
||||
//Logger has a dependency on timezone
|
||||
Timezone::init();
|
||||
|
||||
$opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
if(isset($opts["enable-ansi"])){
|
||||
Terminal::init(true);
|
||||
}elseif(isset($opts["disable-ansi"])){
|
||||
|
@@ -1268,7 +1268,7 @@ class Server{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandSender[]|null $recipients
|
||||
* @param CommandSender[]|null $recipients
|
||||
*/
|
||||
public function broadcastMessage(Translatable|string $message, ?array $recipients = null) : int{
|
||||
$recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS);
|
||||
@@ -1321,9 +1321,9 @@ class Server{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
* @param int $fadeOut Duration in ticks for fade-out.
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
* @param int $fadeOut Duration in ticks for fade-out.
|
||||
* @param Player[]|null $recipients
|
||||
*/
|
||||
public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, ?array $recipients = null) : int{
|
||||
|
@@ -44,7 +44,7 @@ final class ServerConfigGroup{
|
||||
){}
|
||||
|
||||
/**
|
||||
* @param mixed $defaultValue
|
||||
* @param mixed $defaultValue
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@@ -31,9 +31,9 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.9.2";
|
||||
public const BASE_VERSION = "4.12.5";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
private function __construct(){
|
||||
//NOOP
|
||||
|
@@ -87,7 +87,7 @@ abstract class BaseBanner extends Transparent{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
*
|
||||
* @phpstan-param list<BannerPatternLayer> $patterns
|
||||
* @return $this
|
||||
|
@@ -26,6 +26,7 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\utils\CoralType;
|
||||
use pocketmine\block\utils\CoralTypeTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\event\block\BlockDeathEvent;
|
||||
use pocketmine\item\Item;
|
||||
|
||||
abstract class BaseCoral extends Transparent{
|
||||
@@ -50,7 +51,11 @@ abstract class BaseCoral extends Transparent{
|
||||
|
||||
//TODO: check water inside the block itself (not supported on the API yet)
|
||||
if(!$hasWater){
|
||||
$world->setBlock($this->position, $this->setDead(true));
|
||||
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ class Block{
|
||||
protected ?array $collisionBoxes = null;
|
||||
|
||||
/**
|
||||
* @param string $name English name of the block type (TODO: implement translations)
|
||||
* @param string $name English name of the block type (TODO: implement translations)
|
||||
*/
|
||||
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
|
||||
if(($idInfo->getVariant() & $this->getStateBitmask()) !== 0){
|
||||
|
@@ -968,7 +968,7 @@ class BlockFactory{
|
||||
* NOTE: If you are registering a new block type, you will need to add it to the creative inventory yourself - it
|
||||
* will not automatically appear there.
|
||||
*
|
||||
* @param bool $override Whether to override existing registrations
|
||||
* @param bool $override Whether to override existing registrations
|
||||
*
|
||||
* @throws \RuntimeException if something attempted to override an already-registered block without specifying the
|
||||
* $override parameter.
|
||||
|
@@ -27,6 +27,7 @@ use pocketmine\block\utils\CoralType;
|
||||
use pocketmine\block\utils\CoralTypeTrait;
|
||||
use pocketmine\block\utils\InvalidBlockStateException;
|
||||
use pocketmine\data\bedrock\CoralTypeIdMap;
|
||||
use pocketmine\event\block\BlockDeathEvent;
|
||||
use pocketmine\item\Item;
|
||||
use function mt_rand;
|
||||
|
||||
@@ -77,7 +78,11 @@ final class CoralBlock extends Opaque{
|
||||
}
|
||||
}
|
||||
if(!$hasWater){
|
||||
$world->setBlock($this->position, $this->setDead(true));
|
||||
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$world->setBlock($this->position, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,9 @@ use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\ItemFrameAddItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRemoveItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRotateItemSound;
|
||||
use function is_infinite;
|
||||
use function is_nan;
|
||||
use function lcg_value;
|
||||
@@ -136,8 +139,12 @@ class ItemFrame extends Flowable{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->framedItem !== null){
|
||||
$this->itemRotation = ($this->itemRotation + 1) % self::ROTATIONS;
|
||||
|
||||
$this->position->getWorld()->addSound($this->position, new ItemFrameRotateItemSound());
|
||||
}elseif(!$item->isNull()){
|
||||
$this->framedItem = $item->pop();
|
||||
|
||||
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
@@ -154,6 +161,7 @@ class ItemFrame extends Flowable{
|
||||
$world = $this->position->getWorld();
|
||||
if(lcg_value() <= $this->itemDropChance){
|
||||
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
||||
$world->addSound($this->position, new ItemFrameRemoveItemSound());
|
||||
}
|
||||
$this->setFramedItem(null);
|
||||
$world->setBlock($this->position, $this);
|
||||
|
@@ -84,6 +84,7 @@ class Leaves extends Transparent{
|
||||
/**
|
||||
* @param true[] $visited reference parameter
|
||||
* @phpstan-param array<int, true> $visited
|
||||
* @phpstan-param-out array<int, true> $visited
|
||||
*/
|
||||
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
|
||||
$index = World::blockHash($pos->x, $pos->y, $pos->z);
|
||||
|
@@ -135,7 +135,7 @@ class Banner extends Spawnable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
*
|
||||
* @phpstan-param list<BannerPatternLayer> $patterns
|
||||
*/
|
||||
|
@@ -26,7 +26,7 @@ namespace pocketmine\command;
|
||||
interface CommandExecutor{
|
||||
|
||||
/**
|
||||
* @param string[] $args
|
||||
* @param string[] $args
|
||||
*/
|
||||
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool;
|
||||
|
||||
|
@@ -28,9 +28,9 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
@@ -52,7 +52,7 @@ class FormattedCommandAlias extends Command{
|
||||
string $alias,
|
||||
private array $formatStrings
|
||||
){
|
||||
parent::__construct($alias);
|
||||
parent::__construct($alias, KnownTranslationFactory::pocketmine_command_userDefined_description());
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@@ -62,7 +62,20 @@ class FormattedCommandAlias extends Command{
|
||||
foreach($this->formatStrings as $formatString){
|
||||
try{
|
||||
$formatArgs = CommandStringHelper::parseQuoteAware($formatString);
|
||||
$commands[] = array_map(fn(string $formatArg) => $this->buildCommand($formatArg, $args), $formatArgs);
|
||||
$unresolved = [];
|
||||
$processedArgs = [];
|
||||
foreach($formatArgs as $formatArg){
|
||||
$processedArg = $this->buildCommand($formatArg, $args);
|
||||
if($processedArg === null){
|
||||
$unresolved[] = $formatArg;
|
||||
}elseif(count($unresolved) !== 0){
|
||||
//unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
|
||||
throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
|
||||
}else{
|
||||
$processedArgs[] = $processedArg;
|
||||
}
|
||||
}
|
||||
$commands[] = $processedArgs;
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$sender->sendMessage(TextFormat::RED . $e->getMessage());
|
||||
return false;
|
||||
@@ -107,7 +120,7 @@ class FormattedCommandAlias extends Command{
|
||||
/**
|
||||
* @param string[] $args
|
||||
*/
|
||||
private function buildCommand(string $formatString, array $args) : string{
|
||||
private function buildCommand(string $formatString, array $args) : ?string{
|
||||
$index = 0;
|
||||
while(($index = strpos($formatString, '$', $index)) !== false){
|
||||
$start = $index;
|
||||
@@ -129,6 +142,9 @@ class FormattedCommandAlias extends Command{
|
||||
}
|
||||
|
||||
$replacement = self::buildReplacement($args, $position, $rest);
|
||||
if($replacement === null){
|
||||
return null;
|
||||
}
|
||||
|
||||
$end = $index + strlen($fullPlaceholder);
|
||||
$formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
|
||||
@@ -143,9 +159,9 @@ class FormattedCommandAlias extends Command{
|
||||
* @param string[] $args
|
||||
* @phpstan-param list<string> $args
|
||||
*/
|
||||
private static function buildReplacement(array $args, int $position, bool $rest) : string{
|
||||
$replacement = "";
|
||||
private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
|
||||
if($rest && $position < count($args)){
|
||||
$replacement = "";
|
||||
for($i = $position, $c = count($args); $i < $c; ++$i){
|
||||
if($i !== $position){
|
||||
$replacement .= " ";
|
||||
@@ -153,11 +169,13 @@ class FormattedCommandAlias extends Command{
|
||||
|
||||
$replacement .= $args[$i];
|
||||
}
|
||||
|
||||
return $replacement;
|
||||
}elseif($position < count($args)){
|
||||
$replacement .= $args[$position];
|
||||
return $args[$position];
|
||||
}
|
||||
|
||||
return $replacement;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -139,7 +139,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
|
||||
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool{
|
||||
if($label === null){
|
||||
$label = $command->getName();
|
||||
$label = $command->getLabel();
|
||||
}
|
||||
$label = trim($label);
|
||||
$fallbackPrefix = strtolower(trim($fallbackPrefix));
|
||||
|
@@ -72,6 +72,11 @@ class GamemodeCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
if($target->getGamemode()->equals($gameMode)){
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
|
||||
return true;
|
||||
}
|
||||
|
||||
$target->setGamemode($gameMode);
|
||||
if(!$gameMode->equals($target->getGamemode())){
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
|
||||
|
@@ -76,11 +76,10 @@ class HelpCommand extends VanillaCommand{
|
||||
$pageHeight = $sender->getScreenLineHeight();
|
||||
|
||||
if($commandName === ""){
|
||||
/** @var Command[][] $commands */
|
||||
$commands = [];
|
||||
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
|
||||
if($command->testPermissionSilent($sender)){
|
||||
$commands[$command->getName()] = $command;
|
||||
$commands[$command->getLabel()] = $command;
|
||||
}
|
||||
}
|
||||
ksort($commands, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
@@ -95,7 +94,7 @@ class HelpCommand extends VanillaCommand{
|
||||
foreach($commands[$pageNumber - 1] as $command){
|
||||
$description = $command->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::RESET . $descriptionString);
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +105,7 @@ class HelpCommand extends VanillaCommand{
|
||||
$lang = $sender->getLanguage();
|
||||
$description = $cmd->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getName())
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
@@ -82,10 +82,10 @@ class StatusCommand extends VanillaCommand{
|
||||
$sender->sendMessage(TextFormat::GOLD . "Uptime: " . TextFormat::RED . $uptime);
|
||||
|
||||
$tpsColor = TextFormat::GREEN;
|
||||
if($server->getTicksPerSecond() < 17){
|
||||
$tpsColor = TextFormat::GOLD;
|
||||
}elseif($server->getTicksPerSecond() < 12){
|
||||
if($server->getTicksPerSecond() < 12){
|
||||
$tpsColor = TextFormat::RED;
|
||||
}elseif($server->getTicksPerSecond() < 17){
|
||||
$tpsColor = TextFormat::GOLD;
|
||||
}
|
||||
|
||||
$sender->sendMessage(TextFormat::GOLD . "Current TPS: {$tpsColor}{$server->getTicksPerSecond()} ({$server->getTickUsage()}%)");
|
||||
|
@@ -201,7 +201,7 @@ class CraftingManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $outputs
|
||||
* @param Item[] $outputs
|
||||
*/
|
||||
public function matchRecipe(CraftingGrid $grid, array $outputs) : ?CraftingRecipe{
|
||||
//TODO: try to match special recipes before anything else (first they need to be implemented!)
|
||||
|
@@ -46,15 +46,15 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
/**
|
||||
* Constructs a ShapedRecipe instance.
|
||||
*
|
||||
* @param string[] $shape <br>
|
||||
* Array of 1, 2, or 3 strings representing the rows of the recipe.
|
||||
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
|
||||
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
|
||||
* @param string[] $shape <br>
|
||||
* Array of 1, 2, or 3 strings representing the rows of the recipe.
|
||||
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
|
||||
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
|
||||
* @param Item[] $ingredients <br>
|
||||
* Char => Item map of items to be set into the shape.
|
||||
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
|
||||
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
|
||||
* @param Item[] $results List of items that this recipe produces when crafted.
|
||||
* Char => Item map of items to be set into the shape.
|
||||
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
|
||||
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
|
||||
* @param Item[] $results List of items that this recipe produces when crafted.
|
||||
*
|
||||
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
|
||||
*/
|
||||
|
@@ -36,7 +36,8 @@ class ShapelessRecipe implements CraftingRecipe{
|
||||
|
||||
/**
|
||||
* @param Item[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
|
||||
* @param Item[] $results List of result items created by this recipe.
|
||||
* @param Item[] $results List of result items created by this recipe.
|
||||
*
|
||||
* TODO: we'll want to make the type parameter mandatory in PM5
|
||||
*/
|
||||
public function __construct(array $ingredients, array $results, ?ShapelessRecipeType $type = null){
|
||||
|
@@ -43,6 +43,7 @@ use function get_loaded_extensions;
|
||||
use function json_encode;
|
||||
use function ksort;
|
||||
use function max;
|
||||
use function mb_scrub;
|
||||
use function mb_strtoupper;
|
||||
use function microtime;
|
||||
use function ob_end_clean;
|
||||
@@ -196,12 +197,14 @@ class CrashDump{
|
||||
$error["message"] = substr($error["message"], 0, $pos);
|
||||
}
|
||||
}
|
||||
$error["message"] = mb_scrub($error["message"], 'UTF-8');
|
||||
|
||||
if(isset($lastError)){
|
||||
if(isset($lastError["trace"])){
|
||||
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
|
||||
}
|
||||
$this->data->lastError = $lastError;
|
||||
$this->data->lastError["message"] = mb_scrub($this->data->lastError["message"], 'UTF-8');
|
||||
}
|
||||
|
||||
$this->data->error = $error;
|
||||
|
@@ -74,6 +74,7 @@ final class EffectIdMap{
|
||||
//TODO: SLOW_FALLING
|
||||
//TODO: BAD_OMEN
|
||||
//TODO: VILLAGE_HERO
|
||||
$this->register(EffectIds::DARKNESS, VanillaEffects::DARKNESS());
|
||||
}
|
||||
|
||||
//TODO: not a big fan of the code duplication here :(
|
||||
|
@@ -58,4 +58,5 @@ final class EffectIds{
|
||||
public const SLOW_FALLING = 27;
|
||||
public const BAD_OMEN = 28;
|
||||
public const VILLAGE_HERO = 29;
|
||||
public const DARKNESS = 30;
|
||||
}
|
||||
|
@@ -33,13 +33,13 @@ final class PotionTypeIdMap{
|
||||
* @var PotionType[]
|
||||
* @phpstan-var array<int, PotionType>
|
||||
*/
|
||||
private array $idToEnum;
|
||||
private array $idToEnum = [];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private array $enumToId;
|
||||
private array $enumToId = [];
|
||||
|
||||
private function __construct(){
|
||||
$this->register(PotionTypeIds::WATER, PotionType::WATER());
|
||||
|
@@ -52,6 +52,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
@@ -997,9 +998,7 @@ abstract class Entity{
|
||||
|
||||
$this->timings->stopTiming();
|
||||
|
||||
//if($this->isStatic())
|
||||
return ($hasUpdate || $this->hasMovementUpdate());
|
||||
//return !($this instanceof Player);
|
||||
}
|
||||
|
||||
final public function scheduleUpdate() : void{
|
||||
@@ -1479,6 +1478,7 @@ abstract class Entity{
|
||||
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
|
||||
}, $this->attributeMap->getAll()),
|
||||
$this->getAllNetworkData(),
|
||||
new PropertySyncData([], []),
|
||||
[] //TODO: entity links
|
||||
));
|
||||
}
|
||||
@@ -1598,7 +1598,7 @@ abstract class Entity{
|
||||
|
||||
/**
|
||||
* @param Player[]|null $targets
|
||||
* @param MetadataProperty[] $data Properly formatted entity data, defaults to everything
|
||||
* @param MetadataProperty[] $data Properly formatted entity data, defaults to everything
|
||||
*
|
||||
* @phpstan-param array<int, MetadataProperty> $data
|
||||
*/
|
||||
|
@@ -54,6 +54,7 @@ use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\DeviceOS;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
@@ -355,7 +356,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
&& ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){
|
||||
|
||||
$compensation = $this->getHealth() - $source->getFinalDamage() - 1;
|
||||
if($compensation < 0){
|
||||
if($compensation <= -1){
|
||||
$source->setModifier($compensation, EntityDamageEvent::MODIFIER_TOTEM);
|
||||
}
|
||||
}
|
||||
@@ -485,6 +486,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand())),
|
||||
GameMode::SURVIVAL,
|
||||
$this->getAllNetworkData(),
|
||||
new PropertySyncData([], []),
|
||||
UpdateAbilitiesPacket::create(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() /* TODO: this should be unique ID */, [
|
||||
new UpdateAbilitiesPacketLayer(
|
||||
UpdateAbilitiesPacketLayer::LAYER_BASE,
|
||||
|
@@ -35,9 +35,9 @@ class Effect{
|
||||
use NotSerializable;
|
||||
|
||||
/**
|
||||
* @param Translatable|string $name Translation key used for effect name
|
||||
* @param Color $color Color of bubbles given by this effect
|
||||
* @param bool $bad Whether the effect is harmful
|
||||
* @param Translatable|string $name Translation key used for effect name
|
||||
* @param Color $color Color of bubbles given by this effect
|
||||
* @param bool $bad Whether the effect is harmful
|
||||
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
|
||||
*/
|
||||
public function __construct(
|
||||
|
@@ -36,7 +36,7 @@ class EffectInstance{
|
||||
private Color $color;
|
||||
|
||||
/**
|
||||
* @param int|null $duration Passing null will use the effect type's default duration
|
||||
* @param int|null $duration Passing null will use the effect type's default duration
|
||||
*/
|
||||
public function __construct(Effect $effectType, ?int $duration = null, int $amplifier = 0, bool $visible = true, bool $ambient = false, ?Color $overrideColor = null){
|
||||
$this->effectType = $effectType;
|
||||
|
@@ -40,6 +40,7 @@ final class StringToEffectParser extends StringToTParser{
|
||||
$result->register("absorption", fn() => VanillaEffects::ABSORPTION());
|
||||
$result->register("blindness", fn() => VanillaEffects::BLINDNESS());
|
||||
$result->register("conduit_power", fn() => VanillaEffects::CONDUIT_POWER());
|
||||
$result->register("darkness", fn() => VanillaEffects::DARKNESS());
|
||||
$result->register("fatal_poison", fn() => VanillaEffects::FATAL_POISON());
|
||||
$result->register("fire_resistance", fn() => VanillaEffects::FIRE_RESISTANCE());
|
||||
$result->register("haste", fn() => VanillaEffects::HASTE());
|
||||
|
@@ -36,6 +36,7 @@ use pocketmine\utils\RegistryTrait;
|
||||
* @method static AbsorptionEffect ABSORPTION()
|
||||
* @method static Effect BLINDNESS()
|
||||
* @method static Effect CONDUIT_POWER()
|
||||
* @method static Effect DARKNESS()
|
||||
* @method static PoisonEffect FATAL_POISON()
|
||||
* @method static Effect FIRE_RESISTANCE()
|
||||
* @method static Effect HASTE()
|
||||
@@ -68,6 +69,7 @@ final class VanillaEffects{
|
||||
//TODO: bad_omen
|
||||
self::register("blindness", new Effect(KnownTranslationFactory::potion_blindness(), new Color(0x1f, 0x1f, 0x23), true));
|
||||
self::register("conduit_power", new Effect(KnownTranslationFactory::potion_conduitPower(), new Color(0x1d, 0xc2, 0xd1)));
|
||||
self::register("darkness", new Effect(KnownTranslationFactory::effect_darkness(), new Color(0x29, 0x27, 0x21), true, 600, false));
|
||||
self::register("fatal_poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true, 600, true, true));
|
||||
self::register("fire_resistance", new Effect(KnownTranslationFactory::potion_fireResistance(), new Color(0xe4, 0x9a, 0x3a)));
|
||||
self::register("haste", new Effect(KnownTranslationFactory::potion_digSpeed(), new Color(0xd9, 0xc0, 0x43)));
|
||||
|
@@ -38,6 +38,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\ArrowHitSound;
|
||||
use function ceil;
|
||||
use function mt_rand;
|
||||
use function sqrt;
|
||||
|
||||
@@ -103,7 +104,7 @@ class Arrow extends Projectile{
|
||||
}
|
||||
|
||||
public function getResultDamage() : int{
|
||||
$base = parent::getResultDamage();
|
||||
$base = (int) ceil($this->motion->length() * parent::getResultDamage());
|
||||
if($this->isCritical()){
|
||||
return ($base + mt_rand(0, (int) ($base / 2) + 1));
|
||||
}else{
|
||||
|
@@ -128,7 +128,7 @@ abstract class Projectile extends Entity{
|
||||
* Returns the amount of damage this projectile will deal to the entity it hits.
|
||||
*/
|
||||
public function getResultDamage() : int{
|
||||
return (int) ceil($this->motion->length() * $this->damage);
|
||||
return (int) ceil($this->damage);
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
|
@@ -31,6 +31,10 @@ class HandlerList{
|
||||
/** @var RegisteredListener[][] */
|
||||
private array $handlerSlots = [];
|
||||
|
||||
/**
|
||||
* @phpstan-template TEvent of Event
|
||||
* @phpstan-param class-string<TEvent> $class
|
||||
*/
|
||||
public function __construct(
|
||||
private string $class,
|
||||
private ?HandlerList $parentList
|
||||
|
32
src/event/block/BlockDeathEvent.php
Normal file
32
src/event/block/BlockDeathEvent.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\block;
|
||||
|
||||
/**
|
||||
* Called when a block dies.
|
||||
* This could be things like corals dying due to no water being nearby.
|
||||
*/
|
||||
class BlockDeathEvent extends BaseBlockChangeEvent{
|
||||
|
||||
}
|
@@ -23,11 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\block;
|
||||
|
||||
use pocketmine\event\Cancellable;
|
||||
|
||||
/**
|
||||
* Called when plants or crops grow.
|
||||
*/
|
||||
class BlockGrowEvent extends BaseBlockChangeEvent implements Cancellable{
|
||||
class BlockGrowEvent extends BaseBlockChangeEvent{
|
||||
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
|
||||
/**
|
||||
* @param Block[] $blocks
|
||||
* @param float $yield 0-100
|
||||
* @param float $yield 0-100
|
||||
*/
|
||||
public function __construct(Entity $entity, Position $position, array $blocks, float $yield){
|
||||
$this->entity = $entity;
|
||||
|
@@ -35,8 +35,8 @@ class CraftItemEvent extends Event implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Item[] $inputs
|
||||
* @param Item[] $outputs
|
||||
* @param Item[] $inputs
|
||||
* @param Item[] $outputs
|
||||
*/
|
||||
public function __construct(
|
||||
private CraftingTransaction $transaction,
|
||||
|
@@ -26,6 +26,7 @@ namespace pocketmine\event\player;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\lang\KnownTranslationKeys;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
@@ -47,7 +48,7 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
|
||||
/**
|
||||
* @param CommandSender[] $recipients
|
||||
*/
|
||||
public function __construct(Player $player, string $message, array $recipients, string $format = "chat.type.text"){
|
||||
public function __construct(Player $player, string $message, array $recipients, string $format = KnownTranslationKeys::CHAT_TYPE_TEXT){
|
||||
$this->player = $player;
|
||||
$this->message = $message;
|
||||
|
||||
|
@@ -30,7 +30,20 @@ use pocketmine\utils\Utils;
|
||||
use function is_a;
|
||||
|
||||
/**
|
||||
* Allows the creation of players overriding the base Player class
|
||||
* Allows the use of custom Player classes. This enables overriding built-in Player methods to change behaviour that is
|
||||
* not possible to alter any other way.
|
||||
*
|
||||
* You probably don't need this event, and found your way here because you looked at some code in an old plugin that
|
||||
* abused it (very common). Instead of using custom player classes, you should consider making session classes instead.
|
||||
*
|
||||
* @see https://github.com/pmmp/SessionsDemo
|
||||
*
|
||||
* This event is a power-user feature, and multiple plugins using it at the same time will conflict and break unless
|
||||
* they've been designed to work together. This means that it's only usually useful in private plugins.
|
||||
*
|
||||
* WARNING: This should NOT be used for adding extra functions or properties. This is intended for **overriding existing
|
||||
* core behaviour**, and should only be used if you know EXACTLY what you're doing.
|
||||
* Custom player classes may break in any update without warning. This event isn't much more than glorified reflection.
|
||||
*/
|
||||
class PlayerCreationEvent extends Event{
|
||||
|
||||
@@ -54,6 +67,8 @@ class PlayerCreationEvent extends Event{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base class that the final player class must extend.
|
||||
*
|
||||
* @return string
|
||||
* @phpstan-return class-string<Player>
|
||||
*/
|
||||
@@ -62,6 +77,10 @@ class PlayerCreationEvent extends Event{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the class that the final player class must extend.
|
||||
* The new base class must be a subclass of the current base class.
|
||||
* This can (perhaps) be used to limit the options for custom player classes provided by other plugins.
|
||||
*
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<Player> $class
|
||||
*/
|
||||
@@ -74,6 +93,8 @@ class PlayerCreationEvent extends Event{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class that will be instantiated to create the player after the event.
|
||||
*
|
||||
* @return string
|
||||
* @phpstan-return class-string<Player>
|
||||
*/
|
||||
@@ -82,6 +103,9 @@ class PlayerCreationEvent extends Event{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the class that will be instantiated to create the player after the event. The class must not be abstract,
|
||||
* and must be an instance of the base class.
|
||||
*
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<Player> $class
|
||||
*/
|
||||
|
@@ -34,7 +34,7 @@ interface Form extends \JsonSerializable{
|
||||
/**
|
||||
* Handles a form response from a player.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param mixed $data
|
||||
*
|
||||
* @throws FormValidationException if the data could not be processed
|
||||
*/
|
||||
|
@@ -27,6 +27,7 @@ use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function max;
|
||||
@@ -85,6 +86,7 @@ abstract class BaseInventory implements Inventory{
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void{
|
||||
Utils::validateArrayValueType($items, function(Item $item) : void{});
|
||||
if(count($items) > $this->getSize()){
|
||||
$items = array_slice($items, 0, $this->getSize(), true);
|
||||
}
|
||||
|
@@ -33,12 +33,25 @@ use pocketmine\utils\ObjectSet;
|
||||
interface Inventory{
|
||||
public const MAX_STACK = 64;
|
||||
|
||||
/**
|
||||
* Returns the number of slots in the inventory.
|
||||
*/
|
||||
public function getSize() : int;
|
||||
|
||||
/**
|
||||
* Returns the maximum stack size for items in this inventory. Individual item types (such as armor or tools) may
|
||||
* have a smaller maximum stack size.
|
||||
*/
|
||||
public function getMaxStackSize() : int;
|
||||
|
||||
/**
|
||||
* Sets the maximum stack size for items in this inventory.
|
||||
*/
|
||||
public function setMaxStackSize(int $size) : void;
|
||||
|
||||
/**
|
||||
* Returns the item in the specified slot.
|
||||
*/
|
||||
public function getItem(int $index) : Item;
|
||||
|
||||
/**
|
||||
@@ -65,10 +78,11 @@ interface Inventory{
|
||||
public function setContents(array $items) : void;
|
||||
|
||||
/**
|
||||
* Stores the given Items in the inventory. This will try to fill
|
||||
* existing stacks and empty slots as well as it can.
|
||||
* Stores the given Items in the inventory.
|
||||
* This will add to any non-full existing stacks first, and then put the remaining items in empty slots if there are
|
||||
* any available.
|
||||
*
|
||||
* Returns the Items that did not fit.
|
||||
* Returns an array of items which could not fit in the inventory.
|
||||
*
|
||||
* @return Item[]
|
||||
*/
|
||||
@@ -85,15 +99,20 @@ interface Inventory{
|
||||
public function getAddableItemQuantity(Item $item) : int;
|
||||
|
||||
/**
|
||||
* Checks if the inventory contains any Item with the same material data.
|
||||
* It will check id, amount, and metadata (if not null)
|
||||
* Returns whether the total amount of matching items is at least the stack size of the given item. Multiple stacks
|
||||
* of the same item are added together.
|
||||
*
|
||||
* If the input item has specific NBT, only items with the same type and NBT will match. Otherwise, only the item
|
||||
* type is checked.
|
||||
*/
|
||||
public function contains(Item $item) : bool;
|
||||
|
||||
/**
|
||||
* Will return all the Items that has the same id and metadata (if not null).
|
||||
* Won't check amount
|
||||
* The returned array is indexed by slot number.
|
||||
* Returns all matching items in the inventory, irrespective of stack size. The returned array is indexed by slot
|
||||
* number.
|
||||
*
|
||||
* If the input item has specific NBT, only items with the same type and NBT will match. Otherwise, only the item
|
||||
* type is checked.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
@@ -101,10 +120,10 @@ interface Inventory{
|
||||
public function all(Item $item) : array;
|
||||
|
||||
/**
|
||||
* Returns the first slot number containing an item with the same ID, damage (if not any-damage), NBT (if not empty)
|
||||
* and count >= to the count of the specified item stack.
|
||||
* Returns the first slot number containing a matching item with a stack size greater than or equal to the input item.
|
||||
*
|
||||
* If $exact is true, only items with equal ID, damage, NBT and count will match.
|
||||
* If the input item has specific NBT, or if $exact is true, only items with the same type and NBT will match.
|
||||
* Otherwise, only the item type is checked.
|
||||
*/
|
||||
public function first(Item $item, bool $exact = false) : int;
|
||||
|
||||
@@ -119,13 +138,19 @@ interface Inventory{
|
||||
public function isSlotEmpty(int $index) : bool;
|
||||
|
||||
/**
|
||||
* Will remove all the Items that has the same id and metadata (if not null)
|
||||
* Clears all slots containing items equivalent to the given item.
|
||||
*
|
||||
* If the input item has specific NBT, only items with the same type and NBT will match. Otherwise, only the item
|
||||
* type is checked.
|
||||
*/
|
||||
public function remove(Item $item) : void;
|
||||
|
||||
/**
|
||||
* Removes the given Item from the inventory.
|
||||
* It will return the Items that couldn't be removed.
|
||||
* Removes items from the inventory in the amounts specified by the given itemstacks.
|
||||
* Returns an array of items that couldn't be removed.
|
||||
*
|
||||
* If the input item has specific NBT, only items with the same type and NBT will match. Otherwise, only the item
|
||||
* type is checked.
|
||||
*
|
||||
* @return Item[]
|
||||
*/
|
||||
|
@@ -134,6 +134,8 @@ class InventoryTransaction{
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
* @phpstan-param-out Item[] $needItems
|
||||
* @phpstan-param-out Item[] $haveItems
|
||||
*
|
||||
* @throws TransactionValidationException
|
||||
*/
|
||||
|
@@ -75,7 +75,7 @@ class Banner extends ItemBlockWallOrFloor{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
* @param BannerPatternLayer[] $patterns
|
||||
*
|
||||
* @phpstan-param list<BannerPatternLayer> $patterns
|
||||
*
|
||||
|
@@ -109,6 +109,7 @@ abstract class Durable extends Item{
|
||||
*/
|
||||
protected function onBroken() : void{
|
||||
$this->pop();
|
||||
$this->setDamage(0); //the stack size may be greater than 1 if overstacked by a plugin
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +121,7 @@ abstract class Durable extends Item{
|
||||
* Returns whether the item is broken.
|
||||
*/
|
||||
public function isBroken() : bool{
|
||||
return $this->damage >= $this->getMaxDurability();
|
||||
return $this->damage >= $this->getMaxDurability() || $this->isNull();
|
||||
}
|
||||
|
||||
protected function deserializeCompoundTag(CompoundTag $tag) : void{
|
||||
|
@@ -564,7 +564,7 @@ class Item implements \JsonSerializable{
|
||||
/**
|
||||
* Compares an Item to this Item and check if they match.
|
||||
*
|
||||
* @param bool $checkDamage Whether to verify that the damage values match.
|
||||
* @param bool $checkDamage Whether to verify that the damage values match.
|
||||
* @param bool $checkCompound Whether to verify that the items' NBT match.
|
||||
*/
|
||||
final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
|
||||
|
@@ -43,6 +43,34 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ACCEPT_LICENSE, []);
|
||||
}
|
||||
|
||||
public static function action_interact_armorstand_equip() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_ARMORSTAND_EQUIP, []);
|
||||
}
|
||||
|
||||
public static function action_interact_armorstand_pose() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_ARMORSTAND_POSE, []);
|
||||
}
|
||||
|
||||
public static function action_interact_exit_boat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_EXIT_BOAT, []);
|
||||
}
|
||||
|
||||
public static function action_interact_fishing() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_FISHING, []);
|
||||
}
|
||||
|
||||
public static function action_interact_name() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_NAME, []);
|
||||
}
|
||||
|
||||
public static function action_interact_ride_boat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_RIDE_BOAT, []);
|
||||
}
|
||||
|
||||
public static function action_interact_ride_minecart() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ACTION_INTERACT_RIDE_MINECART, []);
|
||||
}
|
||||
|
||||
public static function chat_type_achievement(Translatable|string $param0, Translatable|string $param1) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::CHAT_TYPE_ACHIEVEMENT, [
|
||||
0 => $param0,
|
||||
@@ -627,6 +655,12 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_fallingBlock(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_FALLINGBLOCK, [
|
||||
0 => $param0,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_generic(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_GENERIC, [
|
||||
0 => $param0,
|
||||
@@ -691,6 +725,13 @@ final class KnownTranslationFactory{
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_trident(Translatable|string $param0, Translatable|string $param1) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_TRIDENT, [
|
||||
0 => $param0,
|
||||
1 => $param1,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function death_attack_wither(Translatable|string $param0) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::DEATH_ATTACK_WITHER, [
|
||||
0 => $param0,
|
||||
@@ -743,6 +784,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL, []);
|
||||
}
|
||||
|
||||
public static function effect_darkness() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::EFFECT_DARKNESS, []);
|
||||
}
|
||||
|
||||
public static function enchantment_arrowDamage() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_ARROWDAMAGE, []);
|
||||
}
|
||||
@@ -859,6 +904,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_SOUL_SPEED, []);
|
||||
}
|
||||
|
||||
public static function enchantment_swift_sneak() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_SWIFT_SNEAK, []);
|
||||
}
|
||||
|
||||
public static function enchantment_thorns() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_THORNS, []);
|
||||
}
|
||||
@@ -950,6 +999,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_13_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_5_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_5_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_blocks_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_BLOCKS_DESC, []);
|
||||
}
|
||||
@@ -974,6 +1027,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_MELLOHI_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_otherside_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_OTHERSIDE_DESC, []);
|
||||
}
|
||||
|
||||
public static function item_record_pigstep_desc() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_PIGSTEP_DESC, []);
|
||||
}
|
||||
@@ -1393,6 +1450,10 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_userDefined_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_version_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_VERSION_DESCRIPTION, []);
|
||||
}
|
||||
|
@@ -33,6 +33,13 @@ final class KnownTranslationKeys{
|
||||
public const ABILITY_FLIGHT = "ability.flight";
|
||||
public const ABILITY_NOCLIP = "ability.noclip";
|
||||
public const ACCEPT_LICENSE = "accept_license";
|
||||
public const ACTION_INTERACT_ARMORSTAND_EQUIP = "action.interact.armorstand.equip";
|
||||
public const ACTION_INTERACT_ARMORSTAND_POSE = "action.interact.armorstand.pose";
|
||||
public const ACTION_INTERACT_EXIT_BOAT = "action.interact.exit.boat";
|
||||
public const ACTION_INTERACT_FISHING = "action.interact.fishing";
|
||||
public const ACTION_INTERACT_NAME = "action.interact.name";
|
||||
public const ACTION_INTERACT_RIDE_BOAT = "action.interact.ride.boat";
|
||||
public const ACTION_INTERACT_RIDE_MINECART = "action.interact.ride.minecart";
|
||||
public const CHAT_TYPE_ACHIEVEMENT = "chat.type.achievement";
|
||||
public const CHAT_TYPE_ADMIN = "chat.type.admin";
|
||||
public const CHAT_TYPE_ANNOUNCEMENT = "chat.type.announcement";
|
||||
@@ -138,6 +145,7 @@ final class KnownTranslationKeys{
|
||||
public const DEATH_ATTACK_EXPLOSION = "death.attack.explosion";
|
||||
public const DEATH_ATTACK_EXPLOSION_PLAYER = "death.attack.explosion.player";
|
||||
public const DEATH_ATTACK_FALL = "death.attack.fall";
|
||||
public const DEATH_ATTACK_FALLINGBLOCK = "death.attack.fallingBlock";
|
||||
public const DEATH_ATTACK_GENERIC = "death.attack.generic";
|
||||
public const DEATH_ATTACK_INFIRE = "death.attack.inFire";
|
||||
public const DEATH_ATTACK_INWALL = "death.attack.inWall";
|
||||
@@ -148,6 +156,7 @@ final class KnownTranslationKeys{
|
||||
public const DEATH_ATTACK_OUTOFWORLD = "death.attack.outOfWorld";
|
||||
public const DEATH_ATTACK_PLAYER = "death.attack.player";
|
||||
public const DEATH_ATTACK_PLAYER_ITEM = "death.attack.player.item";
|
||||
public const DEATH_ATTACK_TRIDENT = "death.attack.trident";
|
||||
public const DEATH_ATTACK_WITHER = "death.attack.wither";
|
||||
public const DEATH_FELL_ACCIDENT_GENERIC = "death.fell.accident.generic";
|
||||
public const DEFAULT_GAMEMODE = "default_gamemode";
|
||||
@@ -160,6 +169,7 @@ final class KnownTranslationKeys{
|
||||
public const DISCONNECTIONSCREEN_OUTDATEDSERVER = "disconnectionScreen.outdatedServer";
|
||||
public const DISCONNECTIONSCREEN_RESOURCEPACK = "disconnectionScreen.resourcePack";
|
||||
public const DISCONNECTIONSCREEN_SERVERFULL = "disconnectionScreen.serverFull";
|
||||
public const EFFECT_DARKNESS = "effect.darkness";
|
||||
public const ENCHANTMENT_ARROWDAMAGE = "enchantment.arrowDamage";
|
||||
public const ENCHANTMENT_ARROWFIRE = "enchantment.arrowFire";
|
||||
public const ENCHANTMENT_ARROWINFINITE = "enchantment.arrowInfinite";
|
||||
@@ -189,6 +199,7 @@ final class KnownTranslationKeys{
|
||||
public const ENCHANTMENT_PROTECT_FIRE = "enchantment.protect.fire";
|
||||
public const ENCHANTMENT_PROTECT_PROJECTILE = "enchantment.protect.projectile";
|
||||
public const ENCHANTMENT_SOUL_SPEED = "enchantment.soul_speed";
|
||||
public const ENCHANTMENT_SWIFT_SNEAK = "enchantment.swift_sneak";
|
||||
public const ENCHANTMENT_THORNS = "enchantment.thorns";
|
||||
public const ENCHANTMENT_TRIDENTCHANNELING = "enchantment.tridentChanneling";
|
||||
public const ENCHANTMENT_TRIDENTIMPALING = "enchantment.tridentImpaling";
|
||||
@@ -210,12 +221,14 @@ final class KnownTranslationKeys{
|
||||
public const IP_WARNING = "ip_warning";
|
||||
public const ITEM_RECORD_11_DESC = "item.record_11.desc";
|
||||
public const ITEM_RECORD_13_DESC = "item.record_13.desc";
|
||||
public const ITEM_RECORD_5_DESC = "item.record_5.desc";
|
||||
public const ITEM_RECORD_BLOCKS_DESC = "item.record_blocks.desc";
|
||||
public const ITEM_RECORD_CAT_DESC = "item.record_cat.desc";
|
||||
public const ITEM_RECORD_CHIRP_DESC = "item.record_chirp.desc";
|
||||
public const ITEM_RECORD_FAR_DESC = "item.record_far.desc";
|
||||
public const ITEM_RECORD_MALL_DESC = "item.record_mall.desc";
|
||||
public const ITEM_RECORD_MELLOHI_DESC = "item.record_mellohi.desc";
|
||||
public const ITEM_RECORD_OTHERSIDE_DESC = "item.record_otherside.desc";
|
||||
public const ITEM_RECORD_PIGSTEP_DESC = "item.record_pigstep.desc";
|
||||
public const ITEM_RECORD_STAL_DESC = "item.record_stal.desc";
|
||||
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
|
||||
@@ -306,6 +319,7 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_COMMAND_TRANSFERSERVER_USAGE = "pocketmine.command.transferserver.usage";
|
||||
public const POCKETMINE_COMMAND_UNBAN_IP_DESCRIPTION = "pocketmine.command.unban.ip.description";
|
||||
public const POCKETMINE_COMMAND_UNBAN_PLAYER_DESCRIPTION = "pocketmine.command.unban.player.description";
|
||||
public const POCKETMINE_COMMAND_USERDEFINED_DESCRIPTION = "pocketmine.command.userDefined.description";
|
||||
public const POCKETMINE_COMMAND_VERSION_DESCRIPTION = "pocketmine.command.version.description";
|
||||
public const POCKETMINE_COMMAND_VERSION_MINECRAFTVERSION = "pocketmine.command.version.minecraftVersion";
|
||||
public const POCKETMINE_COMMAND_VERSION_NOSUCHPLUGIN = "pocketmine.command.version.noSuchPlugin";
|
||||
|
@@ -173,6 +173,14 @@ class Language{
|
||||
return $this->internalGet($id) ?? $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return array<string, string>
|
||||
*/
|
||||
public function getAll() : array{
|
||||
return $this->lang;
|
||||
}
|
||||
|
||||
protected function parseTranslation(string $text, ?string $onlyPrefix = null) : string{
|
||||
$newString = "";
|
||||
|
||||
|
@@ -35,7 +35,7 @@ interface AdvancedNetworkInterface extends NetworkInterface{
|
||||
/**
|
||||
* Prevents packets received from the IP address getting processed for the given timeout.
|
||||
*
|
||||
* @param int $timeout Seconds
|
||||
* @param int $timeout Seconds
|
||||
*/
|
||||
public function blockAddress(string $address, int $timeout = 300) : void;
|
||||
|
||||
|
@@ -54,7 +54,7 @@ class Network{
|
||||
|
||||
private BidirectionalBandwidthStatsTracker $bandwidthTracker;
|
||||
private string $name;
|
||||
private NetworkSessionManager$sessionManager;
|
||||
private NetworkSessionManager $sessionManager;
|
||||
|
||||
public function __construct(
|
||||
private \Logger $logger
|
||||
@@ -80,6 +80,10 @@ class Network{
|
||||
return $this->sessionManager->getSessionCount();
|
||||
}
|
||||
|
||||
public function getValidConnectionCount() : int{
|
||||
return $this->sessionManager->getValidSessionCount();
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
foreach($this->interfaces as $interface){
|
||||
$interface->tick();
|
||||
|
@@ -32,12 +32,25 @@ class NetworkSessionManager{
|
||||
/** @var NetworkSession[] */
|
||||
private array $sessions = [];
|
||||
|
||||
/** @var NetworkSession[] */
|
||||
private array $pendingLoginSessions = [];
|
||||
|
||||
/**
|
||||
* Adds a network session to the manager. This should only be called on session creation.
|
||||
*/
|
||||
public function add(NetworkSession $session) : void{
|
||||
$idx = spl_object_id($session);
|
||||
$this->sessions[$idx] = $session;
|
||||
$this->pendingLoginSessions[$idx] = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the session as having sent a login request. After this point, they are counted towards the total player
|
||||
* count.
|
||||
*/
|
||||
public function markLoginReceived(NetworkSession $session) : void{
|
||||
$idx = spl_object_id($session);
|
||||
unset($this->pendingLoginSessions[$idx]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,15 +60,24 @@ class NetworkSessionManager{
|
||||
public function remove(NetworkSession $session) : void{
|
||||
$idx = spl_object_id($session);
|
||||
unset($this->sessions[$idx]);
|
||||
unset($this->pendingLoginSessions[$idx]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of known connected sessions.
|
||||
* Returns the number of known connected sessions, including sessions which have not yet sent a login request.
|
||||
*/
|
||||
public function getSessionCount() : int{
|
||||
return count($this->sessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connected sessions which have either sent a login request, or have already completed the
|
||||
* login sequence.
|
||||
*/
|
||||
public function getValidSessionCount() : int{
|
||||
return count($this->sessions) - count($this->pendingLoginSessions);
|
||||
}
|
||||
|
||||
/** @return NetworkSession[] */
|
||||
public function getSessions() : array{ return $this->sessions; }
|
||||
|
||||
|
@@ -98,6 +98,7 @@ use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
@@ -228,6 +229,7 @@ class NetworkSession{
|
||||
$this->info = $info;
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
$this->manager->markLoginReceived($this);
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
@@ -364,7 +366,7 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
try{
|
||||
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
||||
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){
|
||||
if($packet === null){
|
||||
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
|
||||
throw new PacketHandlingException("Unknown packet received");
|
||||
@@ -877,7 +879,7 @@ class NetworkSession{
|
||||
//TODO: HACK! as of 1.18.10, the client responds differently to the same data ordered in different orders - for
|
||||
//example, sending HEIGHT in the list before FLAGS when unsetting the SWIMMING flag results in a hitbox glitch
|
||||
ksort($properties, SORT_NUMERIC);
|
||||
$this->sendDataPacket(SetActorDataPacket::create($entity->getId(), $properties, 0));
|
||||
$this->sendDataPacket(SetActorDataPacket::create($entity->getId(), $properties, new PropertySyncData([], []), 0));
|
||||
}
|
||||
|
||||
public function onEntityEffectAdded(Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{
|
||||
@@ -896,11 +898,11 @@ class NetworkSession{
|
||||
public function syncAvailableCommands() : void{
|
||||
$commandData = [];
|
||||
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
|
||||
if(isset($commandData[$command->getName()]) || $command->getName() === "help" || !$command->testPermissionSilent($this->player)){
|
||||
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$lname = strtolower($command->getName());
|
||||
$lname = strtolower($command->getLabel());
|
||||
$aliases = $command->getAliases();
|
||||
$aliasObj = null;
|
||||
if(count($aliases) > 0){
|
||||
@@ -908,7 +910,7 @@ class NetworkSession{
|
||||
//work around a client bug which makes the original name not show when aliases are used
|
||||
$aliases[] = $lname;
|
||||
}
|
||||
$aliasObj = new CommandEnum(ucfirst($command->getName()) . "Aliases", array_values($aliases));
|
||||
$aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", array_values($aliases));
|
||||
}
|
||||
|
||||
$description = $command->getDescription();
|
||||
@@ -923,7 +925,7 @@ class NetworkSession{
|
||||
]
|
||||
);
|
||||
|
||||
$commandData[$command->getName()] = $data;
|
||||
$commandData[$command->getLabel()] = $data;
|
||||
}
|
||||
|
||||
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));
|
||||
|
@@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
interface PacketBroadcaster{
|
||||
|
||||
/**
|
||||
* @param NetworkSession[] $recipients
|
||||
* @param NetworkSession[] $recipients
|
||||
* @param ClientboundPacket[] $packets
|
||||
*/
|
||||
public function broadcastPackets(array $recipients, array $packets) : void;
|
||||
|
@@ -118,7 +118,7 @@ final class ItemTranslator{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $simpleMappings
|
||||
* @param int[] $simpleMappings
|
||||
* @param int[][] $complexMappings
|
||||
* @phpstan-param array<string, int> $simpleMappings
|
||||
* @phpstan-param array<string, array<int, int>> $complexMappings
|
||||
@@ -169,6 +169,7 @@ final class ItemTranslator{
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param-out bool $isComplexMapping
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
* @throws TypeConversionException
|
||||
|
@@ -34,6 +34,7 @@ use function openssl_digest;
|
||||
use function openssl_error_string;
|
||||
use function openssl_pkey_derive;
|
||||
use function str_pad;
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
final class EncryptionUtils{
|
||||
|
||||
|
@@ -255,6 +255,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
$useItemTransaction = $packet->getItemInteractionData();
|
||||
if($useItemTransaction !== null){
|
||||
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
|
||||
throw new PacketHandlingException("Too many actions in item use transaction");
|
||||
}
|
||||
$this->inventoryManager->addPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
|
||||
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
|
||||
$packetHandled = false;
|
||||
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
|
||||
@@ -273,6 +277,9 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
$blockActions = $packet->getBlockActions();
|
||||
if($blockActions !== null){
|
||||
if(count($blockActions) > 100){
|
||||
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
|
||||
}
|
||||
foreach($blockActions as $k => $blockAction){
|
||||
$actionHandled = false;
|
||||
if($blockAction instanceof PlayerBlockActionStopBreak){
|
||||
@@ -319,6 +326,12 @@ class InGamePacketHandler extends PacketHandler{
|
||||
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
|
||||
$result = true;
|
||||
|
||||
if(count($packet->trData->getActions()) > 100){
|
||||
throw new PacketHandlingException("Too many actions in inventory transaction");
|
||||
}
|
||||
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($packet->trData->getActions());
|
||||
|
||||
if($packet->trData instanceof NormalTransactionData){
|
||||
$result = $this->handleNormalTransaction($packet->trData);
|
||||
}elseif($packet->trData instanceof MismatchTransactionData){
|
||||
@@ -333,9 +346,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$result = $this->handleReleaseItemTransaction($packet->trData);
|
||||
}
|
||||
|
||||
if(!$result){
|
||||
$this->inventoryManager->syncAll();
|
||||
}else{
|
||||
if($this->craftingTransaction === null){ //don't sync if we're waiting to complete a crafting transaction
|
||||
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
||||
}
|
||||
if($packet->requestId !== 0){
|
||||
@@ -438,7 +449,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
|
||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($data->getActions());
|
||||
|
||||
switch($data->getActionType()){
|
||||
case UseItemTransactionData::ACTION_CLICK_BLOCK:
|
||||
@@ -524,9 +534,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($data->getActions());
|
||||
|
||||
//TODO: use transactiondata for rollbacks here
|
||||
switch($data->getActionType()){
|
||||
case UseItemOnEntityTransactionData::ACTION_INTERACT:
|
||||
$this->player->interactEntity($target, $data->getClickPosition());
|
||||
@@ -541,15 +549,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{
|
||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||
$this->inventoryManager->addRawPredictedSlotChanges($data->getActions());
|
||||
|
||||
//TODO: use transactiondata for rollbacks here (resending entire inventory is very wasteful)
|
||||
switch($data->getActionType()){
|
||||
case ReleaseItemTransactionData::ACTION_RELEASE:
|
||||
if(!$this->player->releaseHeldItem()){
|
||||
$this->inventoryManager->syncContents($this->player->getInventory());
|
||||
}
|
||||
return true;
|
||||
if($data->getActionType() == ReleaseItemTransactionData::ACTION_RELEASE){
|
||||
$this->player->releaseHeldItem();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -123,7 +123,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
$this->session->getPort(),
|
||||
$this->server->requiresAuthentication()
|
||||
);
|
||||
if($this->server->getNetwork()->getConnectionCount() > $this->server->getMaxPlayers()){
|
||||
if($this->server->getNetwork()->getValidConnectionCount() > $this->server->getMaxPlayers()){
|
||||
$ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL);
|
||||
}
|
||||
if(!$this->server->isWhitelisted($playerInfo->getUsername())){
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
|
||||
|
||||
final class SpawnResponsePacketHandler extends PacketHandler{
|
||||
@@ -35,4 +36,10 @@ final class SpawnResponsePacketHandler extends PacketHandler{
|
||||
($this->responseCallback)();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
|
||||
//the client will send this every tick once we start sending chunks, but we don't handle it in this stage
|
||||
//this is very spammy so we filter it out
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -52,6 +52,7 @@ use function mt_rand;
|
||||
use function random_bytes;
|
||||
use function rtrim;
|
||||
use function substr;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
/**
|
||||
|
@@ -40,6 +40,7 @@ use function strlen;
|
||||
use function time;
|
||||
use function trim;
|
||||
use const AF_INET;
|
||||
use const AF_INET6;
|
||||
use const IPPROTO_IPV6;
|
||||
use const IPV6_V6ONLY;
|
||||
use const PHP_INT_MAX;
|
||||
|
@@ -73,6 +73,12 @@ use function sprintf;
|
||||
use function strlen;
|
||||
use function trim;
|
||||
use const AF_INET;
|
||||
use const PREG_BACKTRACK_LIMIT_ERROR;
|
||||
use const PREG_BAD_UTF8_ERROR;
|
||||
use const PREG_BAD_UTF8_OFFSET_ERROR;
|
||||
use const PREG_INTERNAL_ERROR;
|
||||
use const PREG_JIT_STACKLIMIT_ERROR;
|
||||
use const PREG_RECURSION_LIMIT_ERROR;
|
||||
use const SO_RCVTIMEO;
|
||||
use const SOCK_DGRAM;
|
||||
use const SOCKET_ETIMEDOUT;
|
||||
@@ -150,6 +156,7 @@ class UPnP{
|
||||
throw new UPnPException("Failed to recognize the port number from the router's url: {$location}");
|
||||
}
|
||||
$urlPort = $url['port'];
|
||||
$err = "";
|
||||
$response = Internet::getURL($location, 3, [], $err);
|
||||
if($response === null){
|
||||
throw new UPnPException("Unable to access XML: {$err}");
|
||||
|
@@ -1390,7 +1390,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
/**
|
||||
* Returns whether the player can interact with the specified position. This checks distance and direction.
|
||||
*
|
||||
* @param float $maxDiff defaults to half of the 3D diagonal width of a block
|
||||
* @param float $maxDiff defaults to half of the 3D diagonal width of a block
|
||||
*/
|
||||
public function canInteract(Vector3 $pos, float $maxDistance, float $maxDiff = M_SQRT3 / 2) : bool{
|
||||
$eyePos = $this->getEyePos();
|
||||
@@ -1916,9 +1916,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
/**
|
||||
* Adds a title text to the user's screen, with an optional subtitle.
|
||||
*
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
* @param int $fadeOut Duration in ticks for fade-out.
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
* @param int $fadeOut Duration in ticks for fade-out.
|
||||
*/
|
||||
public function sendTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1) : void{
|
||||
$this->setTitleDuration($fadeIn, $stay, $fadeOut);
|
||||
@@ -1959,8 +1959,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
/**
|
||||
* Sets the title duration.
|
||||
*
|
||||
* @param int $fadeIn Title fade-in time in ticks.
|
||||
* @param int $stay Title stay time in ticks.
|
||||
* @param int $fadeIn Title fade-in time in ticks.
|
||||
* @param int $stay Title stay time in ticks.
|
||||
* @param int $fadeOut Title fade-out time in ticks.
|
||||
*/
|
||||
public function setTitleDuration(int $fadeIn, int $stay, int $fadeOut) : void{
|
||||
@@ -2061,7 +2061,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* Transfers a player to another server.
|
||||
*
|
||||
* @param string $address The IP address or hostname of the destination server
|
||||
* @param int $port The destination port, defaults to 19132
|
||||
* @param int $port The destination port, defaults to 19132
|
||||
* @param string $message Message to show in the console when closing the player
|
||||
*
|
||||
* @return bool if transfer was successful.
|
||||
@@ -2105,7 +2105,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
*
|
||||
* Note for internals developers: Do not call this from network sessions. It will cause a feedback loop.
|
||||
*
|
||||
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
|
||||
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
*/
|
||||
public function disconnect(string $reason, Translatable|string|null $quitMessage = null) : void{
|
||||
@@ -2121,7 +2121,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* @internal
|
||||
* This method executes post-disconnect actions and cleanups.
|
||||
*
|
||||
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
|
||||
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
*/
|
||||
public function onPostDisconnect(string $reason, Translatable|string|null $quitMessage) : void{
|
||||
@@ -2416,9 +2416,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->ySize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function teleport(Vector3 $pos, ?float $yaw = null, ?float $pitch = null) : bool{
|
||||
if(parent::teleport($pos, $yaw, $pitch)){
|
||||
|
||||
|
@@ -45,6 +45,7 @@ use function stream_copy_to_stream;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
private bool $isEnabled = false;
|
||||
@@ -201,7 +202,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $args
|
||||
* @param string[] $args
|
||||
*/
|
||||
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool{
|
||||
return false;
|
||||
|
@@ -70,8 +70,7 @@ class PluginGraylist{
|
||||
}
|
||||
$isWhitelist = match($array["mode"]){
|
||||
"whitelist" => true,
|
||||
"blacklist" => false,
|
||||
default => throw new \InvalidArgumentException("\"mode\" must be either \"whitelist\" or \"blacklist\"")
|
||||
"blacklist" => false
|
||||
};
|
||||
$plugins = [];
|
||||
if(isset($array["plugins"])){
|
||||
|
@@ -319,6 +319,9 @@ class PluginManager{
|
||||
/**
|
||||
* @param string[][] $dependencyLists
|
||||
* @param Plugin[] $loadedPlugins
|
||||
*
|
||||
* @phpstan-param array<string, list<string>> $dependencyLists
|
||||
* @phpstan-param-out array<string, list<string>> $dependencyLists
|
||||
*/
|
||||
private function checkDepsForTriage(string $pluginName, string $dependencyType, array &$dependencyLists, array $loadedPlugins, PluginLoadTriage $triage) : void{
|
||||
if(isset($dependencyLists[$pluginName])){
|
||||
|
@@ -57,7 +57,7 @@ interface ResourcePack{
|
||||
* Note that resource packs must **always** be in zip archive format for sending.
|
||||
* A folder resource loader may need to perform on-the-fly compression for this purpose.
|
||||
*
|
||||
* @param int $start Offset to start reading the chunk from
|
||||
* @param int $start Offset to start reading the chunk from
|
||||
* @param int $length Maximum length of data to return.
|
||||
*
|
||||
* @return string byte-array
|
||||
|
@@ -58,7 +58,7 @@ class ResourcePackManager{
|
||||
private array $encryptionKeys = [];
|
||||
|
||||
/**
|
||||
* @param string $path Path to resource-packs directory.
|
||||
* @param string $path Path to resource-packs directory.
|
||||
*/
|
||||
public function __construct(string $path, \Logger $logger){
|
||||
$this->path = $path;
|
||||
|
@@ -187,7 +187,7 @@ abstract class AsyncTask extends \Threaded{
|
||||
* {@link AsyncTask::onCompletion} is called.
|
||||
*
|
||||
* @param mixed $progress The parameter passed to {@link AsyncTask#publishProgress}. It is serialize()'ed
|
||||
* and then unserialize()'ed, as if it has been cloned.
|
||||
* and then unserialize()'ed, as if it has been cloned.
|
||||
*/
|
||||
public function onProgressUpdate($progress) : void{
|
||||
|
||||
@@ -215,7 +215,7 @@ abstract class AsyncTask extends \Threaded{
|
||||
* Objects stored in this storage can be retrieved using fetchLocal() on the same thread that this method was called
|
||||
* from.
|
||||
*
|
||||
* @param mixed $complexData the data to store
|
||||
* @param mixed $complexData the data to store
|
||||
*/
|
||||
protected function storeLocal(string $key, $complexData) : void{
|
||||
if(self::$threadLocalStorage === null){
|
||||
|
@@ -77,7 +77,7 @@ class AsyncWorker extends Worker{
|
||||
* Saves mixed data into the worker's thread-local object store. This can be used to store objects which you
|
||||
* want to use on this worker thread from multiple AsyncTasks.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function saveToThreadStore(string $identifier, $value) : void{
|
||||
if(\Thread::getCurrentThread() !== $this){
|
||||
|
@@ -26,7 +26,7 @@ namespace pocketmine\scheduler;
|
||||
final class BulkCurlTaskOperation{
|
||||
/**
|
||||
* @param string[] $extraHeaders
|
||||
* @param mixed[] $extraOpts
|
||||
* @param mixed[] $extraOpts
|
||||
* @phpstan-param list<string> $extraHeaders
|
||||
* @phpstan-param array<int, mixed> $extraOpts
|
||||
*/
|
||||
|
@@ -29,6 +29,7 @@ use pocketmine\lang\Translatable;
|
||||
use pocketmine\permission\PermissibleBase;
|
||||
use pocketmine\permission\PermissibleDelegateTrait;
|
||||
use pocketmine\Server;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and
|
||||
|
@@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
/**
|
||||
* This trait offers the same functionality as RegistryTrait, but also clones any returned objects to prevent outside
|
||||
* modification.
|
||||
*/
|
||||
trait CloningRegistryTrait{
|
||||
use RegistryTrait;
|
||||
|
||||
|
@@ -55,6 +55,7 @@ use const CASE_LOWER;
|
||||
use const JSON_BIGINT_AS_STRING;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const YAML_UTF8_ENCODING;
|
||||
|
||||
/**
|
||||
* Config Class for simple config manipulation of multiple formats.
|
||||
@@ -108,8 +109,8 @@ class Config{
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $file Path of the file to be loaded
|
||||
* @param int $type Config type to load, -1 by default (detect)
|
||||
* @param string $file Path of the file to be loaded
|
||||
* @param int $type Config type to load, -1 by default (detect)
|
||||
* @param mixed[] $default Array with the default values that will be written to the file if it did not exist
|
||||
* @phpstan-param array<string, mixed> $default
|
||||
*/
|
||||
@@ -493,9 +494,10 @@ class Config{
|
||||
|
||||
/**
|
||||
* @param mixed[] $default
|
||||
* @param mixed[] $data reference parameter
|
||||
* @param mixed[] $data reference parameter
|
||||
* @phpstan-param array<string, mixed> $default
|
||||
* @phpstan-param array<string, mixed> $data
|
||||
* @phpstan-param-out array<string, mixed> $data
|
||||
*/
|
||||
private function fillDefaults(array $default, &$data) : int{
|
||||
$changed = 0;
|
||||
|
@@ -23,6 +23,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
/**
|
||||
* This trait allows a class to simulate a Java-style enum. Members are exposed as static methods and handled via
|
||||
* __callStatic().
|
||||
*
|
||||
* Classes using this trait need to include \@method tags in their class docblock for every enum member.
|
||||
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
|
||||
*/
|
||||
trait EnumTrait{
|
||||
use RegistryTrait;
|
||||
use NotCloneable;
|
||||
|
@@ -187,9 +187,10 @@ final class Filesystem{
|
||||
* @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied)
|
||||
*/
|
||||
public static function createLockFile(string $lockFilePath) : ?int{
|
||||
$resource = fopen($lockFilePath, "a+b");
|
||||
if($resource === false){
|
||||
throw new \InvalidArgumentException("Invalid lock file path or read/write permissions denied");
|
||||
try{
|
||||
$resource = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($lockFilePath, "a+b"));
|
||||
}catch(\ErrorException $e){
|
||||
throw new \InvalidArgumentException("Failed to open lock file: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
if(!flock($resource, LOCK_EX | LOCK_NB)){
|
||||
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
|
||||
|
@@ -36,7 +36,7 @@ final class Git{
|
||||
/**
|
||||
* Returns the git hash of the currently checked out head of the given repository, or null on failure.
|
||||
*
|
||||
* @param bool $dirty reference parameter, set to whether the repo has local changes
|
||||
* @param bool $dirty reference parameter, set to whether the repo has local changes
|
||||
*/
|
||||
public static function getRepositoryState(string $dir, bool &$dirty) : ?string{
|
||||
if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && $out !== false && strlen($out = trim($out)) === 40){
|
||||
|
@@ -70,7 +70,7 @@ class Internet{
|
||||
public static $online = true;
|
||||
|
||||
/**
|
||||
* Gets the External IP using an external service, it is cached
|
||||
* Lazily gets the External IP using an external service and caches the result
|
||||
*
|
||||
* @param bool $force default false, force IP check even when cached
|
||||
*
|
||||
@@ -139,10 +139,14 @@ class Internet{
|
||||
* GETs an URL using cURL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @param int $timeout default 10
|
||||
* @phpstan-template TErrorVar of mixed
|
||||
*
|
||||
* @param int $timeout default 10
|
||||
* @param string[] $extraHeaders
|
||||
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
|
||||
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
|
||||
* @phpstan-param list<string> $extraHeaders
|
||||
* @phpstan-param TErrorVar $err
|
||||
* @phpstan-param-out TErrorVar|string $err
|
||||
*/
|
||||
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null) : ?InternetRequestResult{
|
||||
try{
|
||||
@@ -157,11 +161,15 @@ class Internet{
|
||||
* POSTs data to an URL
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @phpstan-template TErrorVar of mixed
|
||||
*
|
||||
* @param string[]|string $args
|
||||
* @param string[] $extraHeaders
|
||||
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation.
|
||||
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation.
|
||||
* @phpstan-param string|array<string, string> $args
|
||||
* @phpstan-param list<string> $extraHeaders
|
||||
* @phpstan-param TErrorVar $err
|
||||
* @phpstan-param-out TErrorVar|string $err
|
||||
*/
|
||||
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null) : ?InternetRequestResult{
|
||||
try{
|
||||
@@ -179,7 +187,7 @@ class Internet{
|
||||
* General cURL shorthand function.
|
||||
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
|
||||
*
|
||||
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
|
||||
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
|
||||
* @param string[] $extraHeaders extra headers to send as a plain string array
|
||||
* @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map
|
||||
* @param \Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle.
|
||||
|
@@ -150,8 +150,10 @@ final class Process{
|
||||
|
||||
/**
|
||||
* @param string $command Command to execute
|
||||
* @param string|null $stdout Reference parameter to write stdout to
|
||||
* @param string|null $stderr Reference parameter to write stderr to
|
||||
* @param string|null $stdout Reference parameter to write stdout to
|
||||
* @param string|null $stderr Reference parameter to write stderr to
|
||||
* @phpstan-param-out string $stdout
|
||||
* @phpstan-param-out string $stderr
|
||||
*
|
||||
* @return int process exit code
|
||||
*/
|
||||
|
@@ -115,7 +115,7 @@ class Random{
|
||||
* Returns a random integer between $start and $end
|
||||
*
|
||||
* @param int $start default 0
|
||||
* @param int $end default 0x7fffffff
|
||||
* @param int $end default 0x7fffffff
|
||||
*/
|
||||
public function nextRange(int $start = 0, int $end = 0x7fffffff) : int{
|
||||
return $start + ($this->nextInt() % ($end + 1 - $start));
|
||||
|
@@ -28,6 +28,13 @@ use function count;
|
||||
use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
|
||||
/**
|
||||
* This trait allows a class to simulate object class constants, since PHP doesn't currently support this.
|
||||
* These faux constants are exposed in static class methods, which are handled using __callStatic().
|
||||
*
|
||||
* Classes using this trait need to include \@method tags in their class docblock for every faux constant.
|
||||
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
|
||||
*/
|
||||
trait RegistryTrait{
|
||||
/**
|
||||
* @var object[]
|
||||
|
@@ -166,8 +166,10 @@ abstract class Terminal{
|
||||
case Utils::OS_LINUX:
|
||||
case Utils::OS_MACOS:
|
||||
case Utils::OS_BSD:
|
||||
self::getEscapeCodes();
|
||||
return;
|
||||
if(getenv('TERM') !== false){
|
||||
self::getEscapeCodes();
|
||||
return;
|
||||
}
|
||||
|
||||
case Utils::OS_WINDOWS:
|
||||
case Utils::OS_ANDROID:
|
||||
|
@@ -208,7 +208,7 @@ abstract class Timezone{
|
||||
//That's been a bug in PHP since 2008!
|
||||
foreach(timezone_abbreviations_list() as $zones){
|
||||
foreach($zones as $timezone){
|
||||
if($timezone['offset'] == $offset){
|
||||
if($timezone['timezone_id'] !== null && $timezone['offset'] == $offset){
|
||||
return $timezone['timezone_id'];
|
||||
}
|
||||
}
|
||||
|
@@ -313,7 +313,7 @@ final class Utils{
|
||||
}
|
||||
}elseif(($cpuPresent = @file_get_contents("/sys/devices/system/cpu/present")) !== false){
|
||||
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim($cpuPresent), $matches) > 0){
|
||||
$processors = (int) ($matches[2] - $matches[1]);
|
||||
$processors = ((int) $matches[2]) - ((int) $matches[1]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -539,7 +539,7 @@ final class Utils{
|
||||
* incompatible.
|
||||
*
|
||||
* @param callable|CallbackType $signature Dummy callable with the required parameters and return type
|
||||
* @param callable $subject Callable to check the signature of
|
||||
* @param callable $subject Callable to check the signature of
|
||||
* @phpstan-param anyCallable|CallbackType $signature
|
||||
* @phpstan-param anyCallable $subject
|
||||
*
|
||||
|
@@ -66,6 +66,7 @@ use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\player\ChunkSelector;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\promise\Promise;
|
||||
use pocketmine\promise\PromiseResolver;
|
||||
@@ -317,7 +318,6 @@ class World implements ChunkManager{
|
||||
private int $sleepTicks = 0;
|
||||
|
||||
private int $chunkTickRadius;
|
||||
private int $chunksPerTick;
|
||||
private int $tickedBlocksPerSubchunkPerTick = self::DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK;
|
||||
/**
|
||||
* @var true[]
|
||||
@@ -396,6 +396,9 @@ class World implements ChunkManager{
|
||||
|
||||
/**
|
||||
* @phpstan-param BlockPosHash $hash
|
||||
* @phpstan-param-out int $x
|
||||
* @phpstan-param-out int $y
|
||||
* @phpstan-param-out int $z
|
||||
*/
|
||||
public static function getBlockXYZ(int $hash, ?int &$x, ?int &$y, ?int &$z) : void{
|
||||
[$baseX, $baseY, $baseZ] = morton3d_decode($hash);
|
||||
@@ -410,6 +413,8 @@ class World implements ChunkManager{
|
||||
|
||||
/**
|
||||
* @phpstan-param ChunkPosHash $hash
|
||||
* @phpstan-param-out int $x
|
||||
* @phpstan-param-out int $z
|
||||
*/
|
||||
public static function getXZ(int $hash, ?int &$x, ?int &$z) : void{
|
||||
[$x, $z] = morton2d_decode($hash);
|
||||
@@ -488,7 +493,11 @@ class World implements ChunkManager{
|
||||
|
||||
$cfg = $this->server->getConfigGroup();
|
||||
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
|
||||
$this->chunksPerTick = $cfg->getPropertyInt("chunk-ticking.per-tick", 40);
|
||||
if($cfg->getPropertyInt("chunk-ticking.per-tick", 40) <= 0){
|
||||
//TODO: this needs l10n
|
||||
$this->logger->warning("\"chunk-ticking.per-tick\" setting is deprecated, but you've used it to disable chunk ticking. Set \"chunk-ticking.tick-radius\" to 0 in \"pocketmine.yml\" instead.");
|
||||
$this->chunkTickRadius = 0;
|
||||
}
|
||||
$this->tickedBlocksPerSubchunkPerTick = $cfg->getPropertyInt("chunk-ticking.blocks-per-subchunk-per-tick", self::DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK);
|
||||
$this->maxConcurrentChunkPopulationTasks = $cfg->getPropertyInt("chunk-generation.population-queue-size", 2);
|
||||
|
||||
@@ -634,7 +643,7 @@ class World implements ChunkManager{
|
||||
* Returns a list of players who are in the given filter and also using the chunk containing the target position.
|
||||
* Used for broadcasting sounds and particles with specific targets.
|
||||
*
|
||||
* @param Player[] $allowed
|
||||
* @param Player[] $allowed
|
||||
* @phpstan-param list<Player> $allowed
|
||||
*
|
||||
* @return array<int, Player>
|
||||
@@ -1095,8 +1104,23 @@ class World implements ChunkManager{
|
||||
unset($this->randomTickBlocks[$block->getFullId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the radius of chunks to be ticked around each ticking chunk loader (usually players). This is referred to
|
||||
* as "simulation distance" in the Minecraft: Bedrock world options screen.
|
||||
*/
|
||||
public function getChunkTickRadius() : int{
|
||||
return $this->chunkTickRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of chunks ticked around each ticking chunk loader (usually players).
|
||||
*/
|
||||
public function setChunkTickRadius(int $radius) : void{
|
||||
$this->chunkTickRadius = $radius;
|
||||
}
|
||||
|
||||
private function tickChunks() : void{
|
||||
if($this->chunksPerTick <= 0 || count($this->tickingLoaders) === 0){
|
||||
if($this->chunkTickRadius <= 0 || count($this->tickingLoaders) === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1105,19 +1129,26 @@ class World implements ChunkManager{
|
||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
||||
$chunkTickList = [];
|
||||
|
||||
$chunksPerLoader = min(200, max(1, (int) ((($this->chunksPerTick - count($this->tickingLoaders)) / count($this->tickingLoaders)) + 0.5)));
|
||||
$randRange = 3 + $chunksPerLoader / 30;
|
||||
$randRange = (int) ($randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange);
|
||||
$centerChunks = [];
|
||||
|
||||
$selector = new ChunkSelector();
|
||||
foreach($this->tickingLoaders as $loader){
|
||||
$chunkX = (int) floor($loader->getX()) >> Chunk::COORD_BIT_SIZE;
|
||||
$chunkZ = (int) floor($loader->getZ()) >> Chunk::COORD_BIT_SIZE;
|
||||
$centerChunkX = (int) floor($loader->getX()) >> Chunk::COORD_BIT_SIZE;
|
||||
$centerChunkZ = (int) floor($loader->getZ()) >> Chunk::COORD_BIT_SIZE;
|
||||
$centerChunkPosHash = World::chunkHash($centerChunkX, $centerChunkZ);
|
||||
if(isset($centerChunks[$centerChunkPosHash])){
|
||||
//we already queued chunks in this radius because of a previous loader on the same chunk
|
||||
continue;
|
||||
}
|
||||
$centerChunks[$centerChunkPosHash] = true;
|
||||
|
||||
for($chunk = 0; $chunk < $chunksPerLoader; ++$chunk){
|
||||
$dx = mt_rand(-$randRange, $randRange);
|
||||
$dz = mt_rand(-$randRange, $randRange);
|
||||
$hash = World::chunkHash($dx + $chunkX, $dz + $chunkZ);
|
||||
if(!isset($chunkTickList[$hash]) && isset($this->chunks[$hash]) && $this->isChunkTickable($dx + $chunkX, $dz + $chunkZ)){
|
||||
foreach($selector->selectChunks(
|
||||
$this->chunkTickRadius,
|
||||
$centerChunkX,
|
||||
$centerChunkZ
|
||||
) as $hash){
|
||||
World::getXZ($hash, $chunkX, $chunkZ);
|
||||
if(!isset($chunkTickList[$hash]) && isset($this->chunks[$hash]) && $this->isChunkTickable($chunkX, $chunkZ)){
|
||||
$chunkTickList[$hash] = true;
|
||||
}
|
||||
}
|
||||
@@ -1191,7 +1222,8 @@ class World implements ChunkManager{
|
||||
private function tickChunk(int $chunkX, int $chunkZ) : void{
|
||||
$chunk = $this->getChunk($chunkX, $chunkZ);
|
||||
if($chunk === null){
|
||||
throw new \InvalidArgumentException("Chunk is not loaded");
|
||||
//the chunk may have been unloaded during a previous chunk's update (e.g. during BlockGrowEvent)
|
||||
return;
|
||||
}
|
||||
foreach($this->getChunkEntities($chunkX, $chunkZ) as $entity){
|
||||
$entity->onRandomUpdate();
|
||||
@@ -1603,8 +1635,8 @@ class World implements ChunkManager{
|
||||
* Note: If you're using this for performance-sensitive code, and you're guaranteed to be supplying ints in the
|
||||
* specified vector, consider using {@link getBlockAt} instead for better performance.
|
||||
*
|
||||
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
|
||||
* @param bool $addToCache Whether to cache the block object created by this method call.
|
||||
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
|
||||
* @param bool $addToCache Whether to cache the block object created by this method call.
|
||||
*/
|
||||
public function getBlock(Vector3 $pos, bool $cached = true, bool $addToCache = true) : Block{
|
||||
return $this->getBlockAt((int) floor($pos->x), (int) floor($pos->y), (int) floor($pos->z), $cached, $addToCache);
|
||||
@@ -1616,7 +1648,7 @@ class World implements ChunkManager{
|
||||
* Note for plugin developers: If you are using this method a lot (thousands of times for many positions for
|
||||
* example), you may want to set addToCache to false to avoid using excessive amounts of memory.
|
||||
*
|
||||
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
|
||||
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
|
||||
* @param bool $addToCache Whether to cache the block object created by this method call.
|
||||
*/
|
||||
public function getBlockAt(int $x, int $y, int $z, bool $cached = true, bool $addToCache = true) : Block{
|
||||
@@ -1763,7 +1795,8 @@ class World implements ChunkManager{
|
||||
* Tries to break a block using a item, including Player time checks if available
|
||||
* It'll try to lower the durability if Item is a tool, and set it to Air if broken.
|
||||
*
|
||||
* @param Item $item reference parameter (if null, can break anything)
|
||||
* @param Item $item reference parameter (if null, can break anything)
|
||||
* @phpstan-param-out Item $item
|
||||
*/
|
||||
public function useBreakOn(Vector3 $vector, Item &$item = null, ?Player $player = null, bool $createParticles = false) : bool{
|
||||
$vector = $vector->floor();
|
||||
@@ -1864,8 +1897,8 @@ class World implements ChunkManager{
|
||||
/**
|
||||
* Uses a item on a position and face, placing it or activating the block
|
||||
*
|
||||
* @param Player|null $player default null
|
||||
* @param bool $playSound Whether to play a block-place sound if the block was placed successfully.
|
||||
* @param Player|null $player default null
|
||||
* @param bool $playSound Whether to play a block-place sound if the block was placed successfully.
|
||||
*/
|
||||
public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $clickVector = null, ?Player $player = null, bool $playSound = false) : bool{
|
||||
$blockClicked = $this->getBlock($vector);
|
||||
@@ -2060,8 +2093,8 @@ class World implements ChunkManager{
|
||||
/**
|
||||
* Returns the closest Entity to the specified position, within the given radius.
|
||||
*
|
||||
* @param string $entityType Class of entity to use for instanceof
|
||||
* @param bool $includeDead Whether to include entitites which are dead
|
||||
* @param string $entityType Class of entity to use for instanceof
|
||||
* @param bool $includeDead Whether to include entitites which are dead
|
||||
* @phpstan-template TEntity of Entity
|
||||
* @phpstan-param class-string<TEntity> $entityType
|
||||
*
|
||||
@@ -2778,8 +2811,8 @@ class World implements ChunkManager{
|
||||
if($this->getBlockAt($x, $y, $z)->isFullCube()){
|
||||
if($wasAir){
|
||||
$y++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
$wasAir = true;
|
||||
}
|
||||
|
@@ -166,7 +166,7 @@ class WorldManager{
|
||||
/**
|
||||
* Loads a world from the data directory
|
||||
*
|
||||
* @param bool $autoUpgrade Converts worlds to the default format if the world's format is not writable / deprecated
|
||||
* @param bool $autoUpgrade Converts worlds to the default format if the world's format is not writable / deprecated
|
||||
*
|
||||
* @throws WorldException
|
||||
*/
|
||||
|
@@ -164,8 +164,8 @@ class Chunk{
|
||||
/**
|
||||
* Sets the biome ID at the specified X/Z chunk block coordinates
|
||||
*
|
||||
* @param int $x 0-15
|
||||
* @param int $z 0-15
|
||||
* @param int $x 0-15
|
||||
* @param int $z 0-15
|
||||
* @param int $biomeId 0-255
|
||||
*/
|
||||
public function setBiomeId(int $x, int $z, int $biomeId) : void{
|
||||
|
@@ -191,6 +191,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param-out int $x
|
||||
* @phpstan-param-out int $y
|
||||
* @phpstan-param-out int $z
|
||||
*/
|
||||
protected static function deserializeExtraDataKey(int $chunkVersion, int $key, ?int &$x, ?int &$y, ?int &$z) : void{
|
||||
if($chunkVersion >= ChunkVersion::v1_0_0){
|
||||
$x = ($key >> 12) & 0xf;
|
||||
@@ -499,7 +504,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CompoundTag[] $targets
|
||||
* @param CompoundTag[] $targets
|
||||
*/
|
||||
private function writeTags(array $targets, string $index, \LevelDBWriteBatch $write) : void{
|
||||
if(count($targets) > 0){
|
||||
|
@@ -276,6 +276,8 @@ class RegionLoader{
|
||||
/**
|
||||
* @param int $x reference parameter
|
||||
* @param int $z reference parameter
|
||||
* @phpstan-param-out int $x
|
||||
* @phpstan-param-out int $z
|
||||
*/
|
||||
protected static function getChunkCoords(int $offset, ?int &$x, ?int &$z) : void{
|
||||
$x = $offset & 0x1f;
|
||||
|
@@ -92,6 +92,8 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
|
||||
/**
|
||||
* @param int $regionX reference parameter
|
||||
* @param int $regionZ reference parameter
|
||||
* @phpstan-param-out int $regionX
|
||||
* @phpstan-param-out int $regionZ
|
||||
*/
|
||||
public static function getRegionIndex(int $chunkX, int $chunkZ, &$regionX, &$regionZ) : void{
|
||||
$regionX = $chunkX >> 5;
|
||||
|
@@ -38,7 +38,7 @@ use function preg_match_all;
|
||||
final class FlatGeneratorOptions{
|
||||
|
||||
/**
|
||||
* @param int[] $structure
|
||||
* @param int[] $structure
|
||||
* @param mixed[] $extraOptions
|
||||
* @phpstan-param array<int, int> $structure
|
||||
* @phpstan-param array<string, array<string, string>|true> $extraOptions
|
||||
|
@@ -58,10 +58,10 @@ final class GeneratorManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
|
||||
* @param string $name Alias for this generator type that can be written in configs
|
||||
* @param \Closure $presetValidator Callback to validate generator options for new worlds
|
||||
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
||||
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
|
||||
* @param string $name Alias for this generator type that can be written in configs
|
||||
* @param \Closure $presetValidator Callback to validate generator options for new worlds
|
||||
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
||||
*
|
||||
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
|
||||
*
|
||||
|
@@ -208,8 +208,8 @@ class SkyLightUpdate extends LightUpdate{
|
||||
/**
|
||||
* Recalculates the heightmap for the block column at the specified X/Z chunk coordinates
|
||||
*
|
||||
* @param int $x 0-15
|
||||
* @param int $z 0-15
|
||||
* @param int $x 0-15
|
||||
* @param int $z 0-15
|
||||
* @param \SplFixedArray|bool[] $directSkyLightBlockers
|
||||
* @phpstan-param \SplFixedArray<bool> $directSkyLightBlockers
|
||||
*
|
||||
|
@@ -36,6 +36,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\IntMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
|
||||
|
||||
class FloatingTextParticle implements Particle{
|
||||
@@ -115,6 +116,7 @@ class FloatingTextParticle implements Particle{
|
||||
0,
|
||||
[],
|
||||
$actorMetadata,
|
||||
new PropertySyncData([], []),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
35
src/world/sound/ItemFrameAddItemSound.php
Normal file
35
src/world/sound/ItemFrameAddItemSound.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class ItemFrameAddItemSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ITEMFRAME_ADD_ITEM, 0, $pos)];
|
||||
}
|
||||
}
|
35
src/world/sound/ItemFrameRemoveItemSound.php
Normal file
35
src/world/sound/ItemFrameRemoveItemSound.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class ItemFrameRemoveItemSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ITEMFRAME_REMOVE_ITEM, 0, $pos)];
|
||||
}
|
||||
}
|
35
src/world/sound/ItemFrameRotateItemSound.php
Normal file
35
src/world/sound/ItemFrameRotateItemSound.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelEvent;
|
||||
|
||||
class ItemFrameRotateItemSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::create(LevelEvent::SOUND_ITEMFRAME_ROTATE_ITEM, 0, $pos)];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user