mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Merge branch 'minor-next' into feat/async-events
This commit is contained in:
commit
866d473553
3
.github/workflows/draft-release.yml
vendored
3
.github/workflows/draft-release.yml
vendored
@ -64,6 +64,7 @@ jobs:
|
||||
id: get-pm-version
|
||||
run: |
|
||||
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
|
||||
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
|
||||
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
|
||||
@ -72,7 +73,7 @@ jobs:
|
||||
- name: Generate PHP binary download URL
|
||||
id: php-binary-url
|
||||
run: |
|
||||
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
|
||||
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate build info
|
||||
run: |
|
||||
|
8
.github/workflows/main-php-matrix.yml
vendored
8
.github/workflows/main-php-matrix.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -128,7 +128,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
|
33
.github/workflows/pr-remove-waiting-label.yml
vendored
Normal file
33
.github/workflows/pr-remove-waiting-label.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Remove waiting label from PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: synchronize
|
||||
|
||||
jobs:
|
||||
delabel:
|
||||
name: Remove label
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Remove label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const [owner, repo] = context.payload.repository.full_name.split('/');
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: context.payload.number,
|
||||
name: "Status: Waiting on Author",
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
//probably label wasn't set on the issue
|
||||
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
4
.github/workflows/team-pr-auto-approve.yml
vendored
4
.github/workflows/team-pr-auto-approve.yml
vendored
@ -9,9 +9,9 @@ on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
@ -35,4 +35,4 @@ jobs:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: auto_approve_collaborator_pr
|
||||
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}" }'
|
||||
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "0" }'
|
||||
|
@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*/
|
||||
$options = [
|
||||
"base_version" => VersionInfo::BASE_VERSION,
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
|
||||
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
|
||||
"changelog_file_name" => function() : string{
|
||||
|
@ -122,7 +122,7 @@ if(!is_array($ids)){
|
||||
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
|
||||
}
|
||||
$cleanedIds = [];
|
||||
foreach($ids as $name => $id){
|
||||
foreach(Utils::promoteKeys($ids) as $name => $id){
|
||||
if(!is_string($name) || !is_int($id)){
|
||||
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ $constants = [];
|
||||
* @phpstan-param-out array<string, string> $constants
|
||||
*/
|
||||
function collectProperties(string $prefix, array $properties, array &$constants) : void{
|
||||
foreach($properties as $propertyName => $property){
|
||||
foreach(Utils::promoteKeys($properties) as $propertyName => $property){
|
||||
$fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
|
||||
|
||||
$constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\update_registry_annotations;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use function basename;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
@ -48,6 +49,7 @@ if(count($argv) !== 2){
|
||||
|
||||
/**
|
||||
* @param object[] $members
|
||||
* @phpstan-param array<string, object> $members
|
||||
*/
|
||||
function generateMethodAnnotations(string $namespaceName, array $members) : string{
|
||||
$selfName = basename(__FILE__);
|
||||
@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
|
||||
|
||||
static $lineTmpl = " * @method static %2\$s %s()";
|
||||
$memberLines = [];
|
||||
foreach($members as $name => $member){
|
||||
foreach(Utils::stringifyKeys($members) as $name => $member){
|
||||
$reflect = new \ReflectionClass($member);
|
||||
while($reflect !== false && $reflect->isAnonymous()){
|
||||
$reflect = $reflect->getParentClass();
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a
|
||||
Subproject commit 8a396c63fc5e79ea2849bfca100ea21a49ba2933
|
@ -332,7 +332,7 @@ class MemoryManager{
|
||||
continue;
|
||||
}
|
||||
$methodStatics = [];
|
||||
foreach($method->getStaticVariables() as $name => $variable){
|
||||
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
|
||||
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
if(count($methodStatics) > 0){
|
||||
@ -360,7 +360,7 @@ class MemoryManager{
|
||||
'_SESSION' => true
|
||||
];
|
||||
|
||||
foreach($GLOBALS as $varName => $value){
|
||||
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
|
||||
if(isset($ignoredGlobals[$varName])){
|
||||
continue;
|
||||
}
|
||||
@ -376,7 +376,7 @@ class MemoryManager{
|
||||
$reflect = new \ReflectionFunction($function);
|
||||
|
||||
$vars = [];
|
||||
foreach($reflect->getStaticVariables() as $varName => $variable){
|
||||
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
|
||||
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
if(count($vars) > 0){
|
||||
@ -416,7 +416,7 @@ class MemoryManager{
|
||||
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
foreach($reflect->getStaticVariables() as $name => $variable){
|
||||
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
|
||||
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
}else{
|
||||
@ -499,7 +499,7 @@ class MemoryManager{
|
||||
}
|
||||
$data = [];
|
||||
$numeric = 0;
|
||||
foreach($from as $key => $value){
|
||||
foreach(Utils::promoteKeys($from) as $key => $value){
|
||||
$data[$numeric] = [
|
||||
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
|
||||
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
|
||||
|
@ -737,12 +737,15 @@ class Server{
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
* @phpstan-return array<string, list<string>>
|
||||
*/
|
||||
public function getCommandAliases() : array{
|
||||
$section = $this->configGroup->getProperty(Yml::ALIASES);
|
||||
$result = [];
|
||||
if(is_array($section)){
|
||||
foreach($section as $key => $value){
|
||||
foreach(Utils::promoteKeys($section) as $key => $value){
|
||||
//TODO: more validation needed here
|
||||
//key might not be a string, value might not be list<string>
|
||||
$commands = [];
|
||||
if(is_array($value)){
|
||||
$commands = $value;
|
||||
@ -750,7 +753,7 @@ class Server{
|
||||
$commands[] = (string) $value;
|
||||
}
|
||||
|
||||
$result[$key] = $commands;
|
||||
$result[(string) $key] = $commands;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1095,7 +1098,11 @@ class Server{
|
||||
|
||||
$anyWorldFailedToLoad = false;
|
||||
|
||||
foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
|
||||
foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
|
||||
if(!is_string($name)){
|
||||
//TODO: this probably should be an error
|
||||
continue;
|
||||
}
|
||||
if($options === null){
|
||||
$options = [];
|
||||
}elseif(!is_array($options)){
|
||||
|
@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\AnvilFallSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function lcg_value;
|
||||
use function round;
|
||||
|
||||
class Anvil extends Transparent implements Fallable{
|
||||
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
|
||||
}
|
||||
|
||||
public function onHitGround(FallingBlock $blockEntity) : bool{
|
||||
if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
|
||||
if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
|
||||
if($this->damage !== self::VERY_DAMAGED){
|
||||
$this->damage = $this->damage + 1;
|
||||
}else{
|
||||
|
@ -52,6 +52,9 @@ class CakeWithCandle extends BaseCake{
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($this->lit && $face !== Facing::UP){
|
||||
return true;
|
||||
}
|
||||
if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){
|
||||
return true;
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\utils\Utils;
|
||||
use function intdiv;
|
||||
use function lcg_value;
|
||||
|
||||
class Farmland extends Transparent{
|
||||
public const MAX_WETNESS = 7;
|
||||
@ -148,7 +148,7 @@ class Farmland extends Transparent{
|
||||
}
|
||||
|
||||
public function onEntityLand(Entity $entity) : ?float{
|
||||
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
|
||||
if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
|
||||
$ev = new EntityTrampleFarmlandEvent($entity, $this);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
|
@ -31,13 +31,13 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
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;
|
||||
|
||||
class ItemFrame extends Flowable{
|
||||
use AnyFacingTrait;
|
||||
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
|
||||
return false;
|
||||
}
|
||||
$world = $this->position->getWorld();
|
||||
if(lcg_value() <= $this->itemDropChance){
|
||||
if(Utils::getRandomFloat() <= $this->itemDropChance){
|
||||
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
||||
$world->addSound($this->position, new ItemFrameRemoveItemSound());
|
||||
}
|
||||
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
$drops = parent::getDropsForCompatibleTool($item);
|
||||
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
|
||||
if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
|
||||
$drops[] = clone $this->framedItem;
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,9 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\sound\FizzSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function lcg_value;
|
||||
|
||||
abstract class Liquid extends Transparent{
|
||||
public const MAX_DECAY = 7;
|
||||
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
|
||||
|
||||
protected function liquidCollide(Block $cause, Block $result) : bool{
|
||||
if(BlockEventHelper::form($this, $result, $cause)){
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
|
||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
|
||||
|
||||
/**
|
||||
* @return Block[]
|
||||
* @phpstan-return array<int, Block>
|
||||
*/
|
||||
public function getAllKnownStates() : array{
|
||||
return $this->fullList;
|
||||
|
@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
|
||||
public const SLOT_INPUT = 0;
|
||||
public const SLOT_LAPIS = 1;
|
||||
|
||||
/** @var EnchantingOption[] $options */
|
||||
/**
|
||||
* @var EnchantingOption[] $options
|
||||
* @phpstan-var list<EnchantingOption>
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
public function __construct(Position $holder){
|
||||
|
@ -36,13 +36,17 @@ use function str_contains;
|
||||
class SignText{
|
||||
public const LINE_COUNT = 4;
|
||||
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array{0: string, 1: string, 2: string, 3: string}
|
||||
*/
|
||||
private array $lines;
|
||||
private Color $baseColor;
|
||||
private bool $glowing;
|
||||
|
||||
/**
|
||||
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
|
||||
* @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines
|
||||
*
|
||||
* @throws \InvalidArgumentException if the array size is greater than 4
|
||||
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
|
||||
@ -82,6 +86,7 @@ class SignText{
|
||||
* Returns an array of lines currently on the sign.
|
||||
*
|
||||
* @return string[]
|
||||
* @phpstan-return array{0: string, 1: string, 2: string, 3: string}
|
||||
*/
|
||||
public function getLines() : array{
|
||||
return $this->lines;
|
||||
|
@ -44,10 +44,16 @@ abstract class Command{
|
||||
private string $nextLabel;
|
||||
private string $label;
|
||||
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private array $aliases = [];
|
||||
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private array $activeAliases = [];
|
||||
|
||||
private ?CommandMap $commandMap = null;
|
||||
@ -62,6 +68,7 @@ abstract class Command{
|
||||
|
||||
/**
|
||||
* @param string[] $aliases
|
||||
* @phpstan-param list<string> $aliases
|
||||
*/
|
||||
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
|
||||
$this->name = $name;
|
||||
@ -182,6 +189,7 @@ abstract class Command{
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public function getAliases() : array{
|
||||
return $this->activeAliases;
|
||||
@ -201,6 +209,7 @@ abstract class Command{
|
||||
|
||||
/**
|
||||
* @param string[] $aliases
|
||||
* @phpstan-param list<string> $aliases
|
||||
*/
|
||||
public function setAliases(array $aliases) : void{
|
||||
$this->aliases = $aliases;
|
||||
|
@ -71,6 +71,7 @@ use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
@ -81,7 +82,10 @@ use function trim;
|
||||
|
||||
class SimpleCommandMap implements CommandMap{
|
||||
|
||||
/** @var Command[] */
|
||||
/**
|
||||
* @var Command[]
|
||||
* @phpstan-var array<string, Command>
|
||||
*/
|
||||
protected array $knownCommands = [];
|
||||
|
||||
public function __construct(private Server $server){
|
||||
@ -171,7 +175,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
public function unregister(Command $command) : bool{
|
||||
foreach($this->knownCommands as $lbl => $cmd){
|
||||
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
|
||||
if($cmd === $command){
|
||||
unset($this->knownCommands[$lbl]);
|
||||
}
|
||||
@ -239,6 +243,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
|
||||
/**
|
||||
* @return Command[]
|
||||
* @phpstan-return array<string, Command>
|
||||
*/
|
||||
public function getCommands() : array{
|
||||
return $this->knownCommands;
|
||||
@ -247,7 +252,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
public function registerServerAliases() : void{
|
||||
$values = $this->server->getCommandAliases();
|
||||
|
||||
foreach($values as $alias => $commandStrings){
|
||||
foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
|
||||
if(str_contains($alias, ":")){
|
||||
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
|
||||
continue;
|
||||
|
@ -110,14 +110,15 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param list<Item> $items
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
private static function pack(array $items) : array{
|
||||
/** @var Item[] $result */
|
||||
$result = [];
|
||||
|
||||
foreach($items as $i => $item){
|
||||
foreach($items as $item){
|
||||
foreach($result as $otherItem){
|
||||
if($item->canStackWith($otherItem)){
|
||||
$otherItem->setCount($otherItem->getCount() + $item->getCount());
|
||||
@ -134,6 +135,7 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* @param Item[] $outputs
|
||||
* @phpstan-param list<Item> $outputs
|
||||
*/
|
||||
private static function hashOutputs(array $outputs) : string{
|
||||
$outputs = self::pack($outputs);
|
||||
|
@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{
|
||||
*/
|
||||
private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{
|
||||
$result = [];
|
||||
foreach($data as $i => $item){
|
||||
foreach(Utils::promoteKeys($data) as $i => $item){
|
||||
if(!is_object($item)){
|
||||
throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item));
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ interface CraftingRecipe{
|
||||
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
|
||||
*
|
||||
* @return RecipeIngredient[]
|
||||
* @phpstan-return list<RecipeIngredient>
|
||||
*/
|
||||
public function getIngredientList() : array;
|
||||
|
||||
@ -37,6 +38,7 @@ interface CraftingRecipe{
|
||||
* Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
public function getResultsFor(CraftingGrid $grid) : array;
|
||||
|
||||
|
@ -32,11 +32,20 @@ use function str_contains;
|
||||
use function strlen;
|
||||
|
||||
class ShapedRecipe implements CraftingRecipe{
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private array $shape = [];
|
||||
/** @var RecipeIngredient[] char => RecipeIngredient map */
|
||||
/**
|
||||
* @var RecipeIngredient[] char => RecipeIngredient map
|
||||
* @phpstan-var array<string, RecipeIngredient>
|
||||
*/
|
||||
private array $ingredientList = [];
|
||||
/** @var Item[] */
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var list<Item>
|
||||
*/
|
||||
private array $results = [];
|
||||
|
||||
private int $height;
|
||||
@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
* @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.
|
||||
*
|
||||
* @phpstan-param list<string> $shape
|
||||
* @phpstan-param array<string, RecipeIngredient> $ingredients
|
||||
* @phpstan-param list<Item> $results
|
||||
*/
|
||||
public function __construct(array $shape, array $ingredients, array $results){
|
||||
$this->height = count($shape);
|
||||
@ -84,7 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
$this->shape = $shape;
|
||||
|
||||
foreach($ingredients as $char => $i){
|
||||
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
|
||||
if(!str_contains(implode($this->shape), $char)){
|
||||
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
|
||||
}
|
||||
@ -105,6 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
public function getResults() : array{
|
||||
return Utils::cloneObjectArray($this->results);
|
||||
@ -112,6 +126,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
public function getResultsFor(CraftingGrid $grid) : array{
|
||||
return $this->getResults();
|
||||
@ -119,6 +134,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
/**
|
||||
* @return (RecipeIngredient|null)[][]
|
||||
* @phpstan-return list<list<RecipeIngredient|null>>
|
||||
*/
|
||||
public function getIngredientMap() : array{
|
||||
$ingredients = [];
|
||||
@ -132,9 +148,6 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
return $ingredients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RecipeIngredient[]
|
||||
*/
|
||||
public function getIngredientList() : array{
|
||||
$ingredients = [];
|
||||
|
||||
@ -157,6 +170,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
/**
|
||||
* Returns an array of strings containing characters representing the recipe's shape.
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public function getShape() : array{
|
||||
return $this->shape;
|
||||
|
@ -28,15 +28,24 @@ use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
|
||||
class ShapelessRecipe implements CraftingRecipe{
|
||||
/** @var RecipeIngredient[] */
|
||||
/**
|
||||
* @var RecipeIngredient[]
|
||||
* @phpstan-var list<RecipeIngredient>
|
||||
*/
|
||||
private array $ingredients = [];
|
||||
/** @var Item[] */
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var list<Item>
|
||||
*/
|
||||
private array $results;
|
||||
private ShapelessRecipeType $type;
|
||||
|
||||
/**
|
||||
* @param RecipeIngredient[] $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.
|
||||
*
|
||||
* @phpstan-param list<RecipeIngredient> $ingredients
|
||||
* @phpstan-param list<Item> $results
|
||||
*/
|
||||
public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){
|
||||
$this->type = $type;
|
||||
@ -50,6 +59,7 @@ class ShapelessRecipe implements CraftingRecipe{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
public function getResults() : array{
|
||||
return Utils::cloneObjectArray($this->results);
|
||||
@ -63,9 +73,6 @@ class ShapelessRecipe implements CraftingRecipe{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RecipeIngredient[]
|
||||
*/
|
||||
public function getIngredientList() : array{
|
||||
return $this->ingredients;
|
||||
}
|
||||
|
@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{
|
||||
|
||||
public string $plugin = "";
|
||||
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<int, string>
|
||||
*/
|
||||
public array $code = [];
|
||||
|
||||
/** @var string[] */
|
||||
@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{
|
||||
*/
|
||||
public array $plugins = [];
|
||||
|
||||
/** @var string[] */
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
public array $parameters = [];
|
||||
|
||||
public string $serverDotProperties = "";
|
||||
|
@ -48,7 +48,7 @@ final class ItemTagToIdMap{
|
||||
throw new AssumptionFailedError("Invalid item tag map, expected array");
|
||||
}
|
||||
$cleanMap = [];
|
||||
foreach($map as $tagName => $ids){
|
||||
foreach(Utils::promoteKeys($map) as $tagName => $ids){
|
||||
if(!is_string($tagName)){
|
||||
throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key");
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
@ -43,7 +44,7 @@ abstract class LegacyToStringIdMap{
|
||||
if(!is_array($stringToLegacyId)){
|
||||
throw new AssumptionFailedError("Invalid format of ID map");
|
||||
}
|
||||
foreach($stringToLegacyId as $stringId => $legacyId){
|
||||
foreach(Utils::promoteKeys($stringToLegacyId) as $stringId => $legacyId){
|
||||
if(!is_string($stringId) || !is_int($legacyId)){
|
||||
throw new AssumptionFailedError("ID map should have string keys and int values");
|
||||
}
|
||||
|
@ -266,6 +266,7 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
|
||||
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
|
||||
$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
$this->map1to1Item(Ids::ICE_BOMB, Items::ICE_BOMB());
|
||||
$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
|
||||
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
|
||||
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
|
||||
|
@ -56,7 +56,7 @@ final class R12ItemIdToBlockIdMap{
|
||||
}
|
||||
|
||||
$builtMap = [];
|
||||
foreach($map as $itemId => $blockId){
|
||||
foreach(Utils::promoteKeys($map) as $itemId => $blockId){
|
||||
if(!is_string($itemId)){
|
||||
throw new AssumptionFailedError("Invalid blockitem ID mapping table, expected string as key");
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ use function deg2rad;
|
||||
use function floor;
|
||||
use function fmod;
|
||||
use function get_class;
|
||||
use function lcg_value;
|
||||
use function sin;
|
||||
use function spl_object_id;
|
||||
use const M_PI_2;
|
||||
@ -103,7 +102,10 @@ abstract class Entity{
|
||||
return self::$entityCount++;
|
||||
}
|
||||
|
||||
/** @var Player[] */
|
||||
/**
|
||||
* @var Player[]
|
||||
* @phpstan-var array<int, Player>
|
||||
*/
|
||||
protected array $hasSpawned = [];
|
||||
|
||||
protected int $id;
|
||||
@ -907,7 +909,7 @@ abstract class Entity{
|
||||
return false;
|
||||
}
|
||||
|
||||
$force = lcg_value() * 0.2 + 0.1;
|
||||
$force = Utils::getRandomFloat() * 0.2 + 0.1;
|
||||
|
||||
$this->motion = match($direction){
|
||||
Facing::WEST => $this->motion->withComponents(-$force, null, null),
|
||||
|
@ -43,6 +43,7 @@ use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\entity\projectile\Egg;
|
||||
use pocketmine\entity\projectile\EnderPearl;
|
||||
use pocketmine\entity\projectile\ExperienceBottle;
|
||||
use pocketmine\entity\projectile\IceBomb;
|
||||
use pocketmine\entity\projectile\Snowball;
|
||||
use pocketmine\entity\projectile\SplashPotion;
|
||||
use pocketmine\item\Item;
|
||||
@ -120,6 +121,10 @@ final class EntityFactory{
|
||||
return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
|
||||
}, ['FallingSand', 'minecraft:falling_block']);
|
||||
|
||||
$this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{
|
||||
return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt);
|
||||
}, ['minecraft:ice_bomb']);
|
||||
|
||||
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
|
||||
$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
|
||||
if($itemTag === null){
|
||||
|
@ -58,6 +58,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\sound\BurpSound;
|
||||
use pocketmine\world\sound\EntityLandSound;
|
||||
use pocketmine\world\sound\EntityLongFallSound;
|
||||
@ -69,7 +70,6 @@ use function ceil;
|
||||
use function count;
|
||||
use function floor;
|
||||
use function ksort;
|
||||
use function lcg_value;
|
||||
use function max;
|
||||
use function min;
|
||||
use function mt_getrandmax;
|
||||
@ -490,7 +490,7 @@ abstract class Living extends Entity{
|
||||
$helmet = $this->armorInventory->getHelmet();
|
||||
if($helmet instanceof Armor){
|
||||
$finalDamage = $source->getFinalDamage();
|
||||
$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2));
|
||||
$this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
|
||||
$this->armorInventory->setHelmet($helmet);
|
||||
}
|
||||
}
|
||||
@ -697,7 +697,7 @@ abstract class Living extends Entity{
|
||||
$this->setBreathing(false);
|
||||
|
||||
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
|
||||
lcg_value() <= (1 / ($respirationLevel + 1))
|
||||
Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
|
||||
){
|
||||
$ticks -= $tickDiff;
|
||||
if($ticks <= -20){
|
||||
|
86
src/entity/projectile/IceBomb.php
Normal file
86
src/entity/projectile/IceBomb.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?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\entity\projectile;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockTypeIds;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\event\entity\ProjectileHitEvent;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\RayTraceResult;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\world\particle\ItemBreakParticle;
|
||||
use pocketmine\world\sound\IceBombHitSound;
|
||||
|
||||
class IceBomb extends Throwable{
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::ICE_BOMB; }
|
||||
|
||||
public function getResultDamage() : int{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
|
||||
if($block->getTypeId() === BlockTypeIds::WATER){
|
||||
$pos = $block->getPosition();
|
||||
|
||||
return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end);
|
||||
}
|
||||
|
||||
return parent::calculateInterceptWithBlock($block, $start, $end);
|
||||
}
|
||||
|
||||
protected function onHit(ProjectileHitEvent $event) : void{
|
||||
$world = $this->getWorld();
|
||||
$pos = $this->location;
|
||||
|
||||
$world->addSound($pos, new IceBombHitSound());
|
||||
$itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB());
|
||||
for($i = 0; $i < 6; ++$i){
|
||||
$world->addParticle($pos, $itemBreakParticle);
|
||||
}
|
||||
}
|
||||
|
||||
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
|
||||
parent::onHitBlock($blockHit, $hitResult);
|
||||
|
||||
$pos = $blockHit->getPosition();
|
||||
$world = $pos->getWorld();
|
||||
$posX = $pos->getFloorX();
|
||||
$posY = $pos->getFloorY();
|
||||
$posZ = $pos->getFloorZ();
|
||||
|
||||
$ice = VanillaBlocks::ICE();
|
||||
for($x = $posX - 1; $x <= $posX + 1; $x++){
|
||||
for($y = $posY - 1; $y <= $posY + 1; $y++){
|
||||
for($z = $posZ - 1; $z <= $posZ + 1; $z++){
|
||||
if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){
|
||||
$world->setBlockAt($x, $y, $z, $ice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -41,7 +41,10 @@ use function spl_object_id;
|
||||
*/
|
||||
abstract class BaseInventory implements Inventory, SlotValidatedInventory{
|
||||
protected int $maxStackSize = Inventory::MAX_STACK;
|
||||
/** @var Player[] */
|
||||
/**
|
||||
* @var Player[]
|
||||
* @phpstan-var array<int, Player>
|
||||
*/
|
||||
protected array $viewers = [];
|
||||
/**
|
||||
* @var InventoryListener[]|ObjectSet
|
||||
@ -286,8 +289,6 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
|
||||
}
|
||||
|
||||
public function removeItem(Item ...$slots) : array{
|
||||
/** @var Item[] $searchItems */
|
||||
/** @var Item[] $slots */
|
||||
$searchItems = [];
|
||||
foreach($slots as $slot){
|
||||
if(!$slot->isNull()){
|
||||
|
@ -36,7 +36,10 @@ final class CreativeInventory{
|
||||
use SingletonTrait;
|
||||
use DestructorCallbackTrait;
|
||||
|
||||
/** @var Item[] */
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var array<int, Item>
|
||||
*/
|
||||
private array $creative = [];
|
||||
|
||||
/** @phpstan-var ObjectSet<\Closure() : void> */
|
||||
@ -69,6 +72,7 @@ final class CreativeInventory{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getAll() : array{
|
||||
return Utils::cloneObjectArray($this->creative);
|
||||
|
@ -57,9 +57,15 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
protected ?CraftingRecipe $recipe = null;
|
||||
protected ?int $repetitions = null;
|
||||
|
||||
/** @var Item[] */
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var list<Item>
|
||||
*/
|
||||
protected array $inputs = [];
|
||||
/** @var Item[] */
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var list<Item>
|
||||
*/
|
||||
protected array $outputs = [];
|
||||
|
||||
private CraftingManager $craftingManager;
|
||||
@ -74,6 +80,9 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
/**
|
||||
* @param Item[] $providedItems
|
||||
* @return Item[]
|
||||
*
|
||||
* @phpstan-param list<Item> $providedItems
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
private static function packItems(array $providedItems) : array{
|
||||
$packedProvidedItems = [];
|
||||
@ -94,6 +103,9 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
/**
|
||||
* @param Item[] $providedItems
|
||||
* @param RecipeIngredient[] $recipeIngredients
|
||||
*
|
||||
* @phpstan-param list<Item> $providedItems
|
||||
* @phpstan-param list<RecipeIngredient> $recipeIngredients
|
||||
*/
|
||||
public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{
|
||||
if(count($recipeIngredients) === 0){
|
||||
@ -172,6 +184,9 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
* @param Item[] $txItems
|
||||
* @param Item[] $recipeItems
|
||||
*
|
||||
* @phpstan-param list<Item> $txItems
|
||||
* @phpstan-param list<Item> $recipeItems
|
||||
*
|
||||
* @throws TransactionValidationException
|
||||
*/
|
||||
protected function matchOutputs(array $txItems, array $recipeItems) : int{
|
||||
|
@ -29,6 +29,7 @@ use pocketmine\inventory\transaction\action\InventoryAction;
|
||||
use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
@ -57,10 +58,16 @@ use function spl_object_id;
|
||||
class InventoryTransaction{
|
||||
protected bool $hasExecuted = false;
|
||||
|
||||
/** @var Inventory[] */
|
||||
/**
|
||||
* @var Inventory[]
|
||||
* @phpstan-var array<int, Inventory>
|
||||
*/
|
||||
protected array $inventories = [];
|
||||
|
||||
/** @var InventoryAction[] */
|
||||
/**
|
||||
* @var InventoryAction[]
|
||||
* @phpstan-var array<int, InventoryAction>
|
||||
*/
|
||||
protected array $actions = [];
|
||||
|
||||
/**
|
||||
@ -81,6 +88,7 @@ class InventoryTransaction{
|
||||
|
||||
/**
|
||||
* @return Inventory[]
|
||||
* @phpstan-return array<int, Inventory>
|
||||
*/
|
||||
public function getInventories() : array{
|
||||
return $this->inventories;
|
||||
@ -93,6 +101,7 @@ class InventoryTransaction{
|
||||
* significance and should not be relied on.
|
||||
*
|
||||
* @return InventoryAction[]
|
||||
* @phpstan-return array<int, InventoryAction>
|
||||
*/
|
||||
public function getActions() : array{
|
||||
return $this->actions;
|
||||
@ -102,6 +111,9 @@ class InventoryTransaction{
|
||||
if(!isset($this->actions[$hash = spl_object_id($action)])){
|
||||
$this->actions[$hash] = $action;
|
||||
$action->onAddToTransaction($this);
|
||||
if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){
|
||||
$this->inventories[$inventoryId] = $action->getInventory();
|
||||
}
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
|
||||
}
|
||||
@ -120,21 +132,11 @@ class InventoryTransaction{
|
||||
$this->actions = $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
|
||||
* involving inventories.
|
||||
*/
|
||||
public function addInventory(Inventory $inventory) : void{
|
||||
if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
|
||||
$this->inventories[$hash] = $inventory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $needItems
|
||||
* @param Item[] $haveItems
|
||||
* @phpstan-param-out Item[] $needItems
|
||||
* @phpstan-param-out Item[] $haveItems
|
||||
* @phpstan-param-out list<Item> $needItems
|
||||
* @phpstan-param-out list<Item> $haveItems
|
||||
*
|
||||
* @throws TransactionValidationException
|
||||
*/
|
||||
@ -188,11 +190,8 @@ class InventoryTransaction{
|
||||
* wrong order), so this method also tries to chain them into order.
|
||||
*/
|
||||
protected function squashDuplicateSlotChanges() : void{
|
||||
/** @var SlotChangeAction[][] $slotChanges */
|
||||
$slotChanges = [];
|
||||
/** @var Inventory[] $inventories */
|
||||
$inventories = [];
|
||||
/** @var int[] $slots */
|
||||
$slots = [];
|
||||
|
||||
foreach($this->actions as $key => $action){
|
||||
@ -203,7 +202,7 @@ class InventoryTransaction{
|
||||
}
|
||||
}
|
||||
|
||||
foreach($slotChanges as $hash => $list){
|
||||
foreach(Utils::stringifyKeys($slotChanges) as $hash => $list){
|
||||
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
|
||||
continue;
|
||||
}
|
||||
@ -233,6 +232,7 @@ class InventoryTransaction{
|
||||
|
||||
/**
|
||||
* @param SlotChangeAction[] $possibleActions
|
||||
* @phpstan-param list<SlotChangeAction> $possibleActions
|
||||
*/
|
||||
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
|
||||
assert(count($possibleActions) > 0);
|
||||
|
@ -60,6 +60,7 @@ abstract class InventoryAction{
|
||||
|
||||
/**
|
||||
* Called when the action is added to the specified InventoryTransaction.
|
||||
* @deprecated
|
||||
*/
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\inventory\transaction\action;
|
||||
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\SlotValidatedInventory;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
use pocketmine\inventory\transaction\TransactionValidationException;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
@ -85,13 +84,6 @@ class SlotChangeAction extends InventoryAction{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this action's target inventory to the transaction's inventory list.
|
||||
*/
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
$transaction->addInventory($this->inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the item into the target inventory.
|
||||
*/
|
||||
|
@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Binary;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
use function mt_rand;
|
||||
|
||||
class Armor extends Durable{
|
||||
@ -129,7 +129,7 @@ class Armor extends Durable{
|
||||
|
||||
$chance = 1 / ($unbreakingLevel + 1);
|
||||
for($i = 0; $i < $amount; ++$i){
|
||||
if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best
|
||||
if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best
|
||||
$negated++;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\item;
|
||||
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
use function min;
|
||||
|
||||
abstract class Durable extends Item{
|
||||
@ -87,7 +87,7 @@ abstract class Durable extends Item{
|
||||
|
||||
$chance = 1 / ($unbreakingLevel + 1);
|
||||
for($i = 0; $i < $amount; ++$i){
|
||||
if(lcg_value() > $chance){
|
||||
if(Utils::getRandomFloat() > $chance){
|
||||
$negated++;
|
||||
}
|
||||
}
|
||||
|
48
src/item/IceBomb.php
Normal file
48
src/item/IceBomb.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\entity\projectile\IceBomb as IceBombEntity;
|
||||
use pocketmine\entity\projectile\Throwable;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
class IceBomb extends ProjectileItem{
|
||||
|
||||
public function getMaxStackSize() : int{
|
||||
return 16;
|
||||
}
|
||||
|
||||
protected function createEntity(Location $location, Player $thrower) : Throwable{
|
||||
return new IceBombEntity($location, $thrower);
|
||||
}
|
||||
|
||||
public function getThrowForce() : float{
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
public function getCooldownTicks() : int{
|
||||
return 10;
|
||||
}
|
||||
}
|
@ -33,7 +33,10 @@ use function spl_object_id;
|
||||
* The primary purpose of this trait is providing scope isolation for the methods it contains.
|
||||
*/
|
||||
trait ItemEnchantmentHandlingTrait{
|
||||
/** @var EnchantmentInstance[] */
|
||||
/**
|
||||
* @var EnchantmentInstance[]
|
||||
* @phpstan-var array<int, EnchantmentInstance>
|
||||
*/
|
||||
protected array $enchantments = [];
|
||||
|
||||
public function hasEnchantments() : bool{
|
||||
@ -79,6 +82,7 @@ trait ItemEnchantmentHandlingTrait{
|
||||
|
||||
/**
|
||||
* @return EnchantmentInstance[]
|
||||
* @phpstan-return array<int, EnchantmentInstance>
|
||||
*/
|
||||
public function getEnchantments() : array{
|
||||
return $this->enchantments;
|
||||
|
@ -326,8 +326,9 @@ final class ItemTypeIds{
|
||||
public const NAME_TAG = 20287;
|
||||
public const GOAT_HORN = 20288;
|
||||
public const END_CRYSTAL = 20289;
|
||||
public const ICE_BOMB = 20290;
|
||||
|
||||
public const FIRST_UNUSED_ITEM_ID = 20290;
|
||||
public const FIRST_UNUSED_ITEM_ID = 20291;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;
|
||||
|
||||
|
@ -29,6 +29,7 @@ use pocketmine\data\bedrock\item\upgrade\ItemDataUpgrader;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function explode;
|
||||
@ -67,7 +68,7 @@ final class LegacyStringToItemParser{
|
||||
$mappings = json_decode($mappingsRaw, true);
|
||||
if(!is_array($mappings)) throw new AssumptionFailedError("Invalid mappings format, expected array");
|
||||
|
||||
foreach($mappings as $name => $id){
|
||||
foreach(Utils::promoteKeys($mappings) as $name => $id){
|
||||
if(!is_string($id)) throw new AssumptionFailedError("Invalid mappings format, expected string values");
|
||||
$result->addMapping((string) $name, $id);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\item;
|
||||
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\entity\effect\VanillaEffects;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
class RottenFlesh extends Food{
|
||||
|
||||
@ -38,7 +38,7 @@ class RottenFlesh extends Food{
|
||||
}
|
||||
|
||||
public function getAdditionalEffects() : array{
|
||||
if(lcg_value() <= 0.8){
|
||||
if(Utils::getRandomFloat() <= 0.8){
|
||||
return [
|
||||
new EffectInstance(VanillaEffects::HUNGER(), 600)
|
||||
];
|
||||
|
@ -27,15 +27,15 @@ use pocketmine\block\Block;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\World;
|
||||
use function lcg_value;
|
||||
|
||||
abstract class SpawnEgg extends Item{
|
||||
|
||||
abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity;
|
||||
|
||||
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
|
||||
$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0);
|
||||
$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0);
|
||||
|
||||
if($this->hasCustomName()){
|
||||
$entity->setNameTag($this->getCustomName());
|
||||
|
@ -1378,6 +1378,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->register("honey_bottle", fn() => Items::HONEY_BOTTLE());
|
||||
$result->register("host_armor_trim_smithing_template", fn() => Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
$result->register("honeycomb", fn() => Items::HONEYCOMB());
|
||||
$result->register("ice_bomb", fn() => Items::ICE_BOMB());
|
||||
$result->register("ink_sac", fn() => Items::INK_SAC());
|
||||
$result->register("iron_axe", fn() => Items::IRON_AXE());
|
||||
$result->register("iron_boots", fn() => Items::IRON_BOOTS());
|
||||
|
@ -192,6 +192,7 @@ use function strtolower;
|
||||
* @method static Item HONEYCOMB()
|
||||
* @method static HoneyBottle HONEY_BOTTLE()
|
||||
* @method static Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE()
|
||||
* @method static IceBomb ICE_BOMB()
|
||||
* @method static Item INK_SAC()
|
||||
* @method static Axe IRON_AXE()
|
||||
* @method static Armor IRON_BOOTS()
|
||||
@ -504,6 +505,7 @@ final class VanillaItems{
|
||||
self::register("heart_of_the_sea", fn(IID $id) => new Item($id, "Heart of the Sea"));
|
||||
self::register("honey_bottle", fn(IID $id) => new HoneyBottle($id, "Honey Bottle"));
|
||||
self::register("honeycomb", fn(IID $id) => new Item($id, "Honeycomb"));
|
||||
self::register("ice_bomb", fn(IID $id) => new IceBomb($id, "Ice Bomb"));
|
||||
self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
|
||||
self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
|
||||
self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
|
||||
|
@ -147,7 +147,7 @@ class Language{
|
||||
$baseText = $this->parseTranslation($str, $onlyPrefix);
|
||||
}
|
||||
|
||||
foreach($params as $i => $p){
|
||||
foreach(Utils::promoteKeys($params) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
@ -161,7 +161,7 @@ class Language{
|
||||
$baseText = $this->parseTranslation($c->getText());
|
||||
}
|
||||
|
||||
foreach($c->getParameters() as $i => $p){
|
||||
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
final class Translatable{
|
||||
/** @var string[]|Translatable[] $params */
|
||||
protected array $params = [];
|
||||
@ -34,7 +36,7 @@ final class Translatable{
|
||||
protected string $text,
|
||||
array $params = []
|
||||
){
|
||||
foreach($params as $k => $param){
|
||||
foreach(Utils::promoteKeys($params) as $k => $param){
|
||||
if(!($param instanceof Translatable)){
|
||||
$this->params[$k] = (string) $param;
|
||||
}else{
|
||||
|
@ -30,10 +30,16 @@ use function spl_object_id;
|
||||
|
||||
class NetworkSessionManager{
|
||||
|
||||
/** @var NetworkSession[] */
|
||||
/**
|
||||
* @var NetworkSession[]
|
||||
* @phpstan-var array<int, NetworkSession>
|
||||
*/
|
||||
private array $sessions = [];
|
||||
|
||||
/** @var NetworkSession[] */
|
||||
/**
|
||||
* @var NetworkSession[]
|
||||
* @phpstan-var array<int, NetworkSession>
|
||||
*/
|
||||
private array $pendingLoginSessions = [];
|
||||
|
||||
/**
|
||||
|
@ -592,7 +592,6 @@ class InventoryManager{
|
||||
$info = $this->trackItemStack($entry, $slot, $itemStack, null);
|
||||
$contents[] = new ItemStackWrapper($info->getStackId(), $itemStack);
|
||||
}
|
||||
$clearSlotWrapper = new ItemStackWrapper(0, ItemStack::null());
|
||||
if($entry->complexSlotMap !== null){
|
||||
foreach($contents as $slotId => $info){
|
||||
$packetSlot = $entry->complexSlotMap->mapCoreToNet($slotId) ?? null;
|
||||
@ -696,6 +695,7 @@ class InventoryManager{
|
||||
|
||||
/**
|
||||
* @param EnchantingOption[] $options
|
||||
* @phpstan-param list<EnchantingOption> $options
|
||||
*/
|
||||
public function syncEnchantingTableOptions(array $options) : void{
|
||||
$protocolOptions = [];
|
||||
|
@ -1092,7 +1092,7 @@ class NetworkSession{
|
||||
|
||||
public function syncAvailableCommands() : void{
|
||||
$commandData = [];
|
||||
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
|
||||
foreach($this->server->getCommandMap()->getCommands() as $command){
|
||||
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
|
||||
continue;
|
||||
}
|
||||
|
@ -53,7 +53,6 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||
|
||||
$compressors = [];
|
||||
|
||||
/** @var NetworkSession[][] $targetsByCompressor */
|
||||
$targetsByCompressor = [];
|
||||
foreach($recipients as $recipient){
|
||||
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
|
||||
|
5
src/network/mcpe/cache/ChunkCache.php
vendored
5
src/network/mcpe/cache/ChunkCache.php
vendored
@ -78,7 +78,10 @@ class ChunkCache implements ChunkListener{
|
||||
}
|
||||
}
|
||||
|
||||
/** @var CompressBatchPromise[] */
|
||||
/**
|
||||
* @var CompressBatchPromise[]
|
||||
* @phpstan-var array<int, CompressBatchPromise>
|
||||
*/
|
||||
private array $caches = [];
|
||||
|
||||
private int $hits = 0;
|
||||
|
@ -28,6 +28,7 @@ use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function ksort;
|
||||
use const SORT_STRING;
|
||||
@ -43,6 +44,7 @@ final class BlockStateDictionaryEntry{
|
||||
|
||||
/**
|
||||
* @param Tag[] $stateProperties
|
||||
* @phpstan-param array<string, Tag> $stateProperties
|
||||
*/
|
||||
public function __construct(
|
||||
private string $stateName,
|
||||
@ -79,6 +81,7 @@ final class BlockStateDictionaryEntry{
|
||||
|
||||
/**
|
||||
* @param Tag[] $properties
|
||||
* @phpstan-param array<string, Tag> $properties
|
||||
*/
|
||||
public static function encodeStateProperties(array $properties) : string{
|
||||
if(count($properties) === 0){
|
||||
@ -87,7 +90,7 @@ final class BlockStateDictionaryEntry{
|
||||
//TODO: make a more efficient encoding - NBT will do for now, but it's not very compact
|
||||
ksort($properties, SORT_STRING);
|
||||
$tag = new CompoundTag();
|
||||
foreach($properties as $k => $v){
|
||||
foreach(Utils::stringifyKeys($properties) as $k => $v){
|
||||
$tag->setTag($k, $v);
|
||||
}
|
||||
return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\convert;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
@ -41,7 +42,7 @@ final class ItemTypeDictionaryFromDataHelper{
|
||||
}
|
||||
|
||||
$params = [];
|
||||
foreach($table as $name => $entry){
|
||||
foreach(Utils::promoteKeys($table) as $name => $entry){
|
||||
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
if(count($blockActions) > 100){
|
||||
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
|
||||
}
|
||||
foreach($blockActions as $k => $blockAction){
|
||||
foreach(Utils::promoteKeys($blockActions) as $k => $blockAction){
|
||||
$actionHandled = false;
|
||||
if($blockAction instanceof PlayerBlockActionStopBreak){
|
||||
$actionHandled = $this->handlePlayerActionFromData($blockAction->getActionType(), new BlockPosition(0, 0, 0), Facing::DOWN);
|
||||
|
@ -54,6 +54,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResp
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_key_first;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
@ -370,7 +371,7 @@ class ItemStackRequestExecutor{
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
public function generateInventoryTransaction() : InventoryTransaction{
|
||||
foreach($this->request->getActions() as $k => $action){
|
||||
foreach(Utils::promoteKeys($this->request->getActions()) as $k => $action){
|
||||
try{
|
||||
$this->processItemStackRequestAction($action);
|
||||
}catch(ItemStackRequestProcessException $e){
|
||||
|
@ -150,7 +150,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
|
||||
/** @var AuthenticationData|null $extraData */
|
||||
$extraData = null;
|
||||
foreach($chain->chain as $k => $jwt){
|
||||
foreach($chain->chain as $jwt){
|
||||
//validate every chain element
|
||||
try{
|
||||
[, $claims, ] = JwtUtils::parse($jwt);
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use function fclose;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
@ -32,7 +33,10 @@ use function strtolower;
|
||||
use function trim;
|
||||
|
||||
class BanList{
|
||||
/** @var BanEntry[] */
|
||||
/**
|
||||
* @var BanEntry[]
|
||||
* @phpstan-var array<string, BanEntry>
|
||||
*/
|
||||
private array $list = [];
|
||||
|
||||
private bool $enabled = true;
|
||||
@ -101,7 +105,7 @@ class BanList{
|
||||
}
|
||||
|
||||
public function removeExpired() : void{
|
||||
foreach($this->list as $name => $entry){
|
||||
foreach(Utils::promoteKeys($this->list) as $name => $entry){
|
||||
if($entry->hasExpired()){
|
||||
unset($this->list[$name]);
|
||||
}
|
||||
|
@ -25,10 +25,14 @@ namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginException;
|
||||
use pocketmine\utils\Utils;
|
||||
use function spl_object_id;
|
||||
|
||||
class PermissionAttachment{
|
||||
/** @var bool[] */
|
||||
/**
|
||||
* @var bool[]
|
||||
* @phpstan-var array<string, bool>
|
||||
*/
|
||||
private array $permissions = [];
|
||||
|
||||
/**
|
||||
@ -60,6 +64,7 @@ class PermissionAttachment{
|
||||
|
||||
/**
|
||||
* @return bool[]
|
||||
* @phpstan-return array<string, bool>
|
||||
*/
|
||||
public function getPermissions() : array{
|
||||
return $this->permissions;
|
||||
@ -78,9 +83,10 @@ class PermissionAttachment{
|
||||
|
||||
/**
|
||||
* @param bool[] $permissions
|
||||
* @phpstan-param array<string, bool> $permissions
|
||||
*/
|
||||
public function setPermissions(array $permissions) : void{
|
||||
foreach($permissions as $key => $value){
|
||||
foreach(Utils::stringifyKeys($permissions) as $key => $value){
|
||||
$this->permissions[$key] = $value;
|
||||
}
|
||||
$this->recalculatePermissibles();
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\permission;
|
||||
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -37,9 +39,15 @@ class PermissionManager{
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** @var Permission[] */
|
||||
/**
|
||||
* @var Permission[]
|
||||
* @phpstan-var array<string, Permission>
|
||||
*/
|
||||
protected array $permissions = [];
|
||||
/** @var PermissibleInternal[][] */
|
||||
/**
|
||||
* @var PermissibleInternal[][]
|
||||
* @phpstan-var array<string, array<int, PermissibleInternal>>
|
||||
*/
|
||||
protected array $permSubs = [];
|
||||
|
||||
public function getPermission(string $name) : ?Permission{
|
||||
@ -64,6 +72,10 @@ class PermissionManager{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Superseded by server chat broadcast channels
|
||||
* @see Server::subscribeToBroadcastChannel()
|
||||
*/
|
||||
public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{
|
||||
if(!isset($this->permSubs[$permission])){
|
||||
$this->permSubs[$permission] = [];
|
||||
@ -71,6 +83,10 @@ class PermissionManager{
|
||||
$this->permSubs[$permission][spl_object_id($permissible)] = $permissible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Superseded by server chat broadcast channels
|
||||
* @see Server::unsubscribeFromBroadcastChannel()
|
||||
*/
|
||||
public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
|
||||
if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
|
||||
if(count($this->permSubs[$permission]) === 1){
|
||||
@ -81,8 +97,12 @@ class PermissionManager{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Superseded by server chat broadcast channels
|
||||
* @see Server::unsubscribeFromAllBroadcastChannels()
|
||||
*/
|
||||
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
|
||||
foreach($this->permSubs as $permission => $subs){
|
||||
foreach(Utils::promoteKeys($this->permSubs) as $permission => $subs){
|
||||
if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
|
||||
unset($this->permSubs[$permission]);
|
||||
}else{
|
||||
@ -92,6 +112,8 @@ class PermissionManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Superseded by server chat broadcast channels
|
||||
* @see Server::getBroadcastChannelSubscribers()
|
||||
* @return PermissibleInternal[]
|
||||
*/
|
||||
public function getPermissionSubscriptions(string $permission) : array{
|
||||
|
@ -1794,7 +1794,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!$this->isCreative() && !$block->getBreakInfo()->breaksInstantly()){
|
||||
if(!$this->isCreative() && !$target->getBreakInfo()->breaksInstantly()){
|
||||
$this->blockBreakHandler = new SurvivalBlockBreakHandler($this, $pos, $target, $face, 16);
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,6 @@ final class ApiVersion{
|
||||
* @return string[]
|
||||
*/
|
||||
public static function checkAmbiguousVersions(array $versions) : array{
|
||||
/** @var VersionString[][] $indexedVersions */
|
||||
$indexedVersions = [];
|
||||
|
||||
foreach($versions as $str){
|
||||
@ -85,9 +84,8 @@ final class ApiVersion{
|
||||
}
|
||||
}
|
||||
|
||||
/** @var VersionString[] $result */
|
||||
$result = [];
|
||||
foreach($indexedVersions as $major => $list){
|
||||
foreach($indexedVersions as $list){
|
||||
if(count($list) > 1){
|
||||
array_push($result, ...$list);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\plugin;
|
||||
use pocketmine\permission\Permission;
|
||||
use pocketmine\permission\PermissionParser;
|
||||
use pocketmine\permission\PermissionParserException;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function get_debug_type;
|
||||
@ -151,7 +152,7 @@ class PluginDescription{
|
||||
$this->compatibleOperatingSystems = array_map("\strval", (array) ($plugin[self::KEY_OS] ?? []));
|
||||
|
||||
if(isset($plugin[self::KEY_COMMANDS]) && is_array($plugin[self::KEY_COMMANDS])){
|
||||
foreach($plugin[self::KEY_COMMANDS] as $commandName => $commandData){
|
||||
foreach(Utils::promoteKeys($plugin[self::KEY_COMMANDS]) as $commandName => $commandData){
|
||||
if(!is_string($commandName)){
|
||||
throw new PluginDescriptionParseException("Invalid Plugin commands, key must be the name of the command");
|
||||
}
|
||||
@ -177,7 +178,7 @@ class PluginDescription{
|
||||
if(isset($plugin[self::KEY_EXTENSIONS])){
|
||||
$extensions = (array) $plugin[self::KEY_EXTENSIONS];
|
||||
$isLinear = $extensions === array_values($extensions);
|
||||
foreach($extensions as $k => $v){
|
||||
foreach(Utils::promoteKeys($extensions) as $k => $v){
|
||||
if($isLinear){
|
||||
$k = $v;
|
||||
$v = "*";
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\plugin;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_flip;
|
||||
use function is_array;
|
||||
use function is_float;
|
||||
@ -77,7 +78,7 @@ class PluginGraylist{
|
||||
if(!is_array($array["plugins"])){
|
||||
throw new \InvalidArgumentException("\"plugins\" must be an array");
|
||||
}
|
||||
foreach($array["plugins"] as $k => $v){
|
||||
foreach(Utils::promoteKeys($array["plugins"]) as $k => $v){
|
||||
if(!is_string($v) && !is_int($v) && !is_float($v)){
|
||||
throw new \InvalidArgumentException("\"plugins\" contains invalid element at position $k");
|
||||
}
|
||||
|
@ -73,10 +73,16 @@ use function strtolower;
|
||||
* Manages all the plugins
|
||||
*/
|
||||
class PluginManager{
|
||||
/** @var Plugin[] */
|
||||
/**
|
||||
* @var Plugin[]
|
||||
* @phpstan-var array<string, Plugin>
|
||||
*/
|
||||
protected array $plugins = [];
|
||||
|
||||
/** @var Plugin[] */
|
||||
/**
|
||||
* @var Plugin[]
|
||||
* @phpstan-var array<string, Plugin>
|
||||
*/
|
||||
protected array $enabledPlugins = [];
|
||||
|
||||
/** @var array<string, array<string, true>> */
|
||||
@ -118,6 +124,7 @@ class PluginManager{
|
||||
|
||||
/**
|
||||
* @return Plugin[]
|
||||
* @phpstan-return array<string, Plugin>
|
||||
*/
|
||||
public function getPlugins() : array{
|
||||
return $this->plugins;
|
||||
@ -531,7 +538,7 @@ class PluginManager{
|
||||
}
|
||||
|
||||
public function tickSchedulers(int $currentTick) : void{
|
||||
foreach($this->enabledPlugins as $pluginName => $p){
|
||||
foreach(Utils::promoteKeys($this->enabledPlugins) as $pluginName => $p){
|
||||
if(isset($this->enabledPlugins[$pluginName])){
|
||||
//the plugin may have been disabled as a result of updating other plugins' schedulers, and therefore
|
||||
//removed from enabledPlugins; however, foreach will still see it due to copy-on-write
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\promise;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -84,7 +85,7 @@ final class Promise{
|
||||
$toResolve = count($promises);
|
||||
$continue = true;
|
||||
|
||||
foreach($promises as $key => $promise){
|
||||
foreach(Utils::promoteKeys($promises) as $key => $promise){
|
||||
$promise->onCompletion(
|
||||
function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
|
||||
$values[$key] = $value;
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\resourcepacks;
|
||||
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_keys;
|
||||
use function copy;
|
||||
@ -87,7 +88,7 @@ class ResourcePackManager{
|
||||
throw new \InvalidArgumentException("\"resource_stack\" key should contain a list of pack names");
|
||||
}
|
||||
|
||||
foreach($resourceStack as $pos => $pack){
|
||||
foreach(Utils::promoteKeys($resourceStack) as $pos => $pack){
|
||||
if(!is_string($pack) && !is_int($pack) && !is_float($pack)){
|
||||
$logger->critical("Found invalid entry in resource pack list at offset $pos of type " . gettype($pack));
|
||||
continue;
|
||||
|
@ -26,8 +26,12 @@ namespace pocketmine\scheduler;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
abstract class Task{
|
||||
/** @phpstan-var TaskHandler<static>|null */
|
||||
private ?TaskHandler $taskHandler = null;
|
||||
|
||||
/**
|
||||
* @phpstan-return TaskHandler<static>|null
|
||||
*/
|
||||
final public function getHandler() : ?TaskHandler{
|
||||
return $this->taskHandler;
|
||||
}
|
||||
@ -36,6 +40,9 @@ abstract class Task{
|
||||
return Utils::getNiceClassName($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TaskHandler<static>|null $taskHandler
|
||||
*/
|
||||
final public function setHandler(?TaskHandler $taskHandler) : void{
|
||||
if($this->taskHandler === null || $taskHandler === null){
|
||||
$this->taskHandler = $taskHandler;
|
||||
|
@ -26,6 +26,9 @@ namespace pocketmine\scheduler;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
|
||||
/**
|
||||
* @template TTask of Task
|
||||
*/
|
||||
class TaskHandler{
|
||||
protected int $nextRun;
|
||||
|
||||
@ -36,6 +39,9 @@ class TaskHandler{
|
||||
private string $taskName;
|
||||
private string $ownerName;
|
||||
|
||||
/**
|
||||
* @phpstan-param TTask $task
|
||||
*/
|
||||
public function __construct(
|
||||
protected Task $task,
|
||||
protected int $delay = -1,
|
||||
@ -66,6 +72,9 @@ class TaskHandler{
|
||||
$this->nextRun = $ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return TTask
|
||||
*/
|
||||
public function getTask() : Task{
|
||||
return $this->task;
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ use pocketmine\utils\ReversePriorityQueue;
|
||||
class TaskScheduler{
|
||||
private bool $enabled = true;
|
||||
|
||||
/** @phpstan-var ReversePriorityQueue<int, TaskHandler> */
|
||||
/** @phpstan-var ReversePriorityQueue<int, TaskHandler<covariant Task>> */
|
||||
protected ReversePriorityQueue $queue;
|
||||
|
||||
/**
|
||||
* @var ObjectSet|TaskHandler[]
|
||||
* @phpstan-var ObjectSet<TaskHandler>
|
||||
* @phpstan-var ObjectSet<TaskHandler<covariant Task>>
|
||||
*/
|
||||
protected ObjectSet $tasks;
|
||||
|
||||
@ -51,18 +51,42 @@ class TaskScheduler{
|
||||
$this->tasks = new ObjectSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TTask $task
|
||||
*
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
public function scheduleTask(Task $task) : TaskHandler{
|
||||
return $this->addTask($task, -1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TTask $task
|
||||
*
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
public function scheduleDelayedTask(Task $task, int $delay) : TaskHandler{
|
||||
return $this->addTask($task, $delay, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TTask $task
|
||||
*
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
public function scheduleRepeatingTask(Task $task, int $period) : TaskHandler{
|
||||
return $this->addTask($task, -1, $period);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TTask $task
|
||||
*
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
public function scheduleDelayedRepeatingTask(Task $task, int $delay, int $period) : TaskHandler{
|
||||
return $this->addTask($task, $delay, $period);
|
||||
}
|
||||
@ -77,10 +101,19 @@ class TaskScheduler{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param TaskHandler<covariant Task> $task
|
||||
*/
|
||||
public function isQueued(TaskHandler $task) : bool{
|
||||
return $this->tasks->contains($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TTask $task
|
||||
*
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
private function addTask(Task $task, int $delay, int $period) : TaskHandler{
|
||||
if(!$this->enabled){
|
||||
throw new \LogicException("Tried to schedule task to disabled scheduler");
|
||||
@ -99,6 +132,11 @@ class TaskScheduler{
|
||||
return $this->handle(new TaskHandler($task, $delay, $period, $this->owner));
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TTask of Task
|
||||
* @phpstan-param TaskHandler<TTask> $handler
|
||||
* @phpstan-return TaskHandler<TTask>
|
||||
*/
|
||||
private function handle(TaskHandler $handler) : TaskHandler{
|
||||
if($handler->isDelayed()){
|
||||
$nextRun = $this->currentTick + $handler->getDelay();
|
||||
@ -128,7 +166,7 @@ class TaskScheduler{
|
||||
}
|
||||
$this->currentTick = $currentTick;
|
||||
while($this->isReady($this->currentTick)){
|
||||
/** @var TaskHandler $task */
|
||||
/** @phpstan-var TaskHandler<covariant Task> $task */
|
||||
$task = $this->queue->extract();
|
||||
if($task->isCancelled()){
|
||||
$this->tasks->remove($task);
|
||||
|
@ -31,6 +31,7 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerboundPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\scheduler\Task;
|
||||
use pocketmine\scheduler\TaskHandler;
|
||||
use function get_class;
|
||||
use function str_starts_with;
|
||||
@ -195,6 +196,10 @@ abstract class Timings{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TTask of Task
|
||||
* @phpstan-param TaskHandler<TTask> $task
|
||||
*/
|
||||
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
|
||||
self::init();
|
||||
$name = "Task: " . $task->getTaskName();
|
||||
|
@ -67,6 +67,8 @@ use function is_nan;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
use function mb_check_encoding;
|
||||
use function mt_getrandmax;
|
||||
use function mt_rand;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_contents;
|
||||
use function ob_start;
|
||||
@ -403,7 +405,7 @@ final class Utils{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $trace
|
||||
* @param mixed[][] $trace
|
||||
* @return string[]
|
||||
*/
|
||||
public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{
|
||||
@ -445,6 +447,7 @@ final class Utils{
|
||||
* @phpstan-param list<array<string, mixed>> $trace
|
||||
*
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
|
||||
$messages = [];
|
||||
@ -456,7 +459,7 @@ final class Utils{
|
||||
}else{
|
||||
$args = $trace[$i]["params"];
|
||||
}
|
||||
/** @var mixed[] $args */
|
||||
/** @phpstan-var array<int, mixed> $args */
|
||||
|
||||
$paramsList = [];
|
||||
$offset = 0;
|
||||
@ -608,6 +611,18 @@ final class Utils{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets rid of PHPStan BenevolentUnionType on array keys, so that wrong type errors get reported properly
|
||||
* Use this if you don't care what the key type is and just want proper PHPStan error reporting
|
||||
*
|
||||
* @phpstan-template TValueType
|
||||
* @phpstan-param array<TValueType> $array
|
||||
* @phpstan-return array<int|string, TValueType>
|
||||
*/
|
||||
public static function promoteKeys(array $array) : array{
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function checkUTF8(string $string) : void{
|
||||
if(!mb_check_encoding($string, 'UTF-8')){
|
||||
throw new \InvalidArgumentException("Text must be valid UTF-8");
|
||||
@ -675,4 +690,12 @@ final class Utils{
|
||||
//jit not available
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random float between 0.0 and 1.0
|
||||
* Drop-in replacement for lcg_value()
|
||||
*/
|
||||
public static function getRandomFloat() : float{
|
||||
return mt_rand() / mt_getrandmax();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,10 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
class BlockTransaction{
|
||||
/** @var Block[][][] */
|
||||
/**
|
||||
* @var Block[][][]
|
||||
* @phpstan-var array<int, array<int, array<int, Block>>>
|
||||
*/
|
||||
private array $blocks = [];
|
||||
|
||||
/**
|
||||
|
@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\utils\ReversePriorityQueue;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\biome\BiomeRegistry;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -120,7 +121,6 @@ use function get_class;
|
||||
use function gettype;
|
||||
use function is_a;
|
||||
use function is_object;
|
||||
use function lcg_value;
|
||||
use function max;
|
||||
use function microtime;
|
||||
use function min;
|
||||
@ -1998,10 +1998,10 @@ class World implements ChunkManager{
|
||||
return null;
|
||||
}
|
||||
|
||||
$itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item);
|
||||
$itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item);
|
||||
|
||||
$itemEntity->setPickupDelay($delay);
|
||||
$itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1));
|
||||
$itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1));
|
||||
$itemEntity->spawnToAll();
|
||||
|
||||
return $itemEntity;
|
||||
@ -2018,9 +2018,9 @@ class World implements ChunkManager{
|
||||
$orbs = [];
|
||||
|
||||
foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){
|
||||
$orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split);
|
||||
$orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split);
|
||||
|
||||
$orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2));
|
||||
$orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2));
|
||||
$orb->spawnToAll();
|
||||
|
||||
$orbs[] = $orb;
|
||||
|
@ -56,7 +56,10 @@ use function trim;
|
||||
class WorldManager{
|
||||
public const TICKS_PER_AUTOSAVE = 300 * Server::TARGET_TICKS_PER_SECOND;
|
||||
|
||||
/** @var World[] */
|
||||
/**
|
||||
* @var World[]
|
||||
* @phpstan-var array<int, World>
|
||||
*/
|
||||
private array $worlds = [];
|
||||
private ?World $defaultWorld = null;
|
||||
|
||||
@ -76,6 +79,7 @@ class WorldManager{
|
||||
|
||||
/**
|
||||
* @return World[]
|
||||
* @phpstan-return array<int, World>
|
||||
*/
|
||||
public function getWorlds() : array{
|
||||
return $this->worlds;
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\world\format;
|
||||
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
|
||||
class SubChunk{
|
||||
@ -36,6 +35,7 @@ class SubChunk{
|
||||
* SubChunk constructor.
|
||||
*
|
||||
* @param PalettedBlockArray[] $blockLayers
|
||||
* @phpstan-param list<PalettedBlockArray> $blockLayers
|
||||
*/
|
||||
public function __construct(
|
||||
private int $emptyBlockId,
|
||||
@ -85,6 +85,7 @@ class SubChunk{
|
||||
|
||||
/**
|
||||
* @return PalettedBlockArray[]
|
||||
* @phpstan-return list<PalettedBlockArray>
|
||||
*/
|
||||
public function getBlockLayers() : array{
|
||||
return $this->blockLayers;
|
||||
@ -129,17 +130,18 @@ class SubChunk{
|
||||
}
|
||||
|
||||
public function collectGarbage() : void{
|
||||
foreach($this->blockLayers as $k => $layer){
|
||||
$cleanedLayers = [];
|
||||
foreach($this->blockLayers as $layer){
|
||||
$layer->collectGarbage();
|
||||
|
||||
foreach($layer->getPalette() as $p){
|
||||
if($p !== $this->emptyBlockId){
|
||||
$cleanedLayers[] = $layer;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
unset($this->blockLayers[$k]);
|
||||
}
|
||||
$this->blockLayers = array_values($this->blockLayers);
|
||||
$this->blockLayers = $cleanedLayers;
|
||||
$this->biomes->collectGarbage();
|
||||
|
||||
if($this->skyLight !== null && $this->skyLight->isUniform(0)){
|
||||
|
@ -65,6 +65,8 @@ abstract class BaseWorldProvider implements WorldProvider{
|
||||
abstract protected function loadLevelData() : WorldData;
|
||||
|
||||
private function translatePalette(PalettedBlockArray $blockArray, \Logger $logger) : PalettedBlockArray{
|
||||
//TODO: missing type info in stubs
|
||||
/** @phpstan-var list<int> $palette */
|
||||
$palette = $blockArray->getPalette();
|
||||
|
||||
$newPalette = [];
|
||||
|
@ -32,6 +32,10 @@ final class ChunkData{
|
||||
* @param SubChunk[] $subChunks
|
||||
* @param CompoundTag[] $entityNBT
|
||||
* @param CompoundTag[] $tileNBT
|
||||
*
|
||||
* @phpstan-param array<int, SubChunk> $subChunks
|
||||
* @phpstan-param list<CompoundTag> $entityNBT
|
||||
* @phpstan-param list<CompoundTag> $tileNBT
|
||||
*/
|
||||
public function __construct(
|
||||
private array $subChunks,
|
||||
@ -42,14 +46,21 @@ final class ChunkData{
|
||||
|
||||
/**
|
||||
* @return SubChunk[]
|
||||
* @phpstan-return array<int, SubChunk>
|
||||
*/
|
||||
public function getSubChunks() : array{ return $this->subChunks; }
|
||||
|
||||
public function isPopulated() : bool{ return $this->populated; }
|
||||
|
||||
/** @return CompoundTag[] */
|
||||
/**
|
||||
* @return CompoundTag[]
|
||||
* @phpstan-return list<CompoundTag>
|
||||
*/
|
||||
public function getEntityNBT() : array{ return $this->entityNBT; }
|
||||
|
||||
/** @return CompoundTag[] */
|
||||
/**
|
||||
* @return CompoundTag[]
|
||||
* @phpstan-return list<CompoundTag>
|
||||
*/
|
||||
public function getTileNBT() : array{ return $this->tileNBT; }
|
||||
}
|
||||
|
@ -31,7 +31,10 @@ use const SORT_NUMERIC;
|
||||
|
||||
final class RegionGarbageMap{
|
||||
|
||||
/** @var RegionLocationTableEntry[] */
|
||||
/**
|
||||
* @var RegionLocationTableEntry[]
|
||||
* @phpstan-var array<int, RegionLocationTableEntry>
|
||||
*/
|
||||
private array $entries = [];
|
||||
private bool $clean = false;
|
||||
|
||||
@ -48,7 +51,6 @@ final class RegionGarbageMap{
|
||||
* @param RegionLocationTableEntry[]|null[] $locationTable
|
||||
*/
|
||||
public static function buildFromLocationTable(array $locationTable) : self{
|
||||
/** @var RegionLocationTableEntry[] $usedMap */
|
||||
$usedMap = [];
|
||||
foreach($locationTable as $entry){
|
||||
if($entry === null){
|
||||
@ -62,12 +64,10 @@ final class RegionGarbageMap{
|
||||
|
||||
ksort($usedMap, SORT_NUMERIC);
|
||||
|
||||
/** @var RegionLocationTableEntry[] $garbageMap */
|
||||
$garbageMap = [];
|
||||
|
||||
/** @var RegionLocationTableEntry|null $prevEntry */
|
||||
$prevEntry = null;
|
||||
foreach($usedMap as $firstSector => $entry){
|
||||
foreach($usedMap as $entry){
|
||||
$prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
|
||||
$currentStart = $entry->getFirstSector();
|
||||
if($prevEndPlusOne < $currentStart){
|
||||
|
@ -67,7 +67,10 @@ class RegionLoader{
|
||||
/** @var resource */
|
||||
protected $filePointer;
|
||||
protected int $nextSector = self::FIRST_SECTOR;
|
||||
/** @var RegionLocationTableEntry[]|null[] */
|
||||
/**
|
||||
* @var RegionLocationTableEntry[]|null[]
|
||||
* @phpstan-var list<RegionLocationTableEntry|null>
|
||||
*/
|
||||
protected array $locationTable = [];
|
||||
protected RegionGarbageMap $garbageTable;
|
||||
public int $lastUsed;
|
||||
@ -327,7 +330,6 @@ class RegionLoader{
|
||||
* @throws CorruptedRegionException
|
||||
*/
|
||||
private function checkLocationTableValidity() : void{
|
||||
/** @var int[] $usedOffsets */
|
||||
$usedOffsets = [];
|
||||
|
||||
$fileSize = filesize($this->filePath);
|
||||
@ -355,7 +357,7 @@ class RegionLoader{
|
||||
}
|
||||
ksort($usedOffsets, SORT_NUMERIC);
|
||||
$prevLocationIndex = null;
|
||||
foreach($usedOffsets as $startOffset => $locationTableIndex){
|
||||
foreach($usedOffsets as $locationTableIndex){
|
||||
if($this->locationTable[$locationTableIndex] === null){
|
||||
continue;
|
||||
}
|
||||
|
@ -72,7 +72,10 @@ abstract class RegionWorldProvider extends BaseWorldProvider{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var RegionLoader[] */
|
||||
/**
|
||||
* @var RegionLoader[]
|
||||
* @phpstan-var array<int, RegionLoader>
|
||||
*/
|
||||
protected array $regions = [];
|
||||
|
||||
protected function loadLevelData() : WorldData{
|
||||
|
@ -76,7 +76,10 @@ class PopulationTask extends AsyncTask{
|
||||
|
||||
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
|
||||
|
||||
/** @var string[] $serialChunks */
|
||||
/**
|
||||
* @var string[] $serialChunks
|
||||
* @phpstan-var array<int, string|null> $serialChunks
|
||||
*/
|
||||
$serialChunks = igbinary_unserialize($this->adjacentChunks);
|
||||
$chunks = array_map(
|
||||
function(?string $serialized) : ?Chunk{
|
||||
@ -92,7 +95,6 @@ class PopulationTask extends AsyncTask{
|
||||
|
||||
self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk);
|
||||
|
||||
/** @var Chunk[] $resultChunks */
|
||||
$resultChunks = []; //this is just to keep phpstan's type inference happy
|
||||
foreach($chunks as $relativeChunkHash => $c){
|
||||
World::getXZ($relativeChunkHash, $relativeX, $relativeZ);
|
||||
|
34
src/world/sound/IceBombHitSound.php
Normal file
34
src/world/sound/IceBombHitSound.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
final class IceBombHitSound implements Sound{
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::ICEBOMB_HIT, $pos, false)];
|
||||
}
|
||||
}
|
@ -801,7 +801,7 @@ parameters:
|
||||
path: ../../../src/scheduler/BulkCurlTask.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\.$#"
|
||||
message: "#^Cannot call method getNextRun\\(\\) on array\\<string, int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\>\\|int\\|pocketmine\\\\scheduler\\\\TaskHandler\\<covariant pocketmine\\\\scheduler\\\\Task\\>\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/scheduler/TaskScheduler.php
|
||||
|
||||
@ -840,11 +840,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/utils/Utils.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$trace of static method pocketmine\\\\utils\\\\Utils\\:\\:printableTrace\\(\\) expects array\\<int, array\\<string, mixed\\>\\>, array given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/utils/Utils.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
|
@ -110,3 +110,8 @@ parameters:
|
||||
count: 2
|
||||
path: ../../phpunit/promise/PromiseTest.php
|
||||
|
||||
-
|
||||
message: "#^Strict comparison using \\=\\=\\= between 0 and 0 will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: ../rules/UnsafeForeachArrayOfStringRule.php
|
||||
|
||||
|
@ -28,6 +28,7 @@ use PhpParser\Node\Stmt\Foreach_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
use PHPStan\Type\BenevolentUnionType;
|
||||
use PHPStan\Type\ClassStringType;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\StringType;
|
||||
@ -62,8 +63,17 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
|
||||
|
||||
$hasCastableKeyTypes = false;
|
||||
$expectsIntKeyTypes = false;
|
||||
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes) : Type{
|
||||
if($type instanceof IntegerType){
|
||||
$implicitType = false;
|
||||
$benevolentUnionDepth = 0;
|
||||
TypeTraverser::map($iterableType->getIterableKeyType(), function(Type $type, callable $traverse) use (&$hasCastableKeyTypes, &$expectsIntKeyTypes, &$benevolentUnionDepth, &$implicitType) : Type{
|
||||
if($type instanceof BenevolentUnionType){
|
||||
$implicitType = true;
|
||||
$benevolentUnionDepth++;
|
||||
$result = $traverse($type);
|
||||
$benevolentUnionDepth--;
|
||||
return $result;
|
||||
}
|
||||
if($type instanceof IntegerType && $benevolentUnionDepth === 0){
|
||||
$expectsIntKeyTypes = true;
|
||||
return $type;
|
||||
}
|
||||
@ -78,12 +88,20 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
|
||||
return $type;
|
||||
});
|
||||
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
|
||||
$func = Utils::stringifyKeys(...);
|
||||
$tip = $implicitType ?
|
||||
sprintf(
|
||||
"Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting",
|
||||
Utils::getNiceClosureName(Utils::promoteKeys(...))
|
||||
) :
|
||||
sprintf(
|
||||
"Use %s() to get a \Generator that will force the keys to string",
|
||||
Utils::getNiceClosureName(Utils::stringifyKeys(...)),
|
||||
);
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
"Unsafe foreach on array with key type %s (they might be casted to int).",
|
||||
$iterableType->getIterableKeyType()->describe(VerbosityLevel::value())
|
||||
))->tip(sprintf("Use %s() for a safe Generator-based iterator.", Utils::getNiceClosureName($func)))->build()
|
||||
))->tip($tip)->build()
|
||||
];
|
||||
}
|
||||
return [];
|
||||
|
@ -135,7 +135,7 @@ class BlockTest extends TestCase{
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
foreach($expected as $typeName => $numStates){
|
||||
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){
|
||||
if(!is_string($typeName) || !is_int($numStates)){
|
||||
throw new AssumptionFailedError("Old table should be array<string, int>");
|
||||
}
|
||||
|
@ -33,15 +33,26 @@ use pocketmine\Server;
|
||||
|
||||
final class EventTest extends TestCase{
|
||||
|
||||
public function testHandlerInheritance() : void{
|
||||
private Plugin $mockPlugin;
|
||||
private PluginManager $pluginManager;
|
||||
|
||||
protected function setUp() : void{
|
||||
HandlerListManager::global()->unregisterAll();
|
||||
|
||||
//TODO: this is a really bad hack and could break any time if PluginManager decides to access its Server field
|
||||
//we really need to make it possible to register events without a Plugin or Server context
|
||||
$mockServer = $this->createMock(Server::class);
|
||||
$mockPlugin = self::createStub(Plugin::class);
|
||||
$mockPlugin->method('isEnabled')->willReturn(true);
|
||||
$this->mockPlugin = self::createStub(Plugin::class);
|
||||
$this->mockPlugin->method('isEnabled')->willReturn(true);
|
||||
|
||||
$pluginManager = new PluginManager($mockServer, null);
|
||||
$this->pluginManager = new PluginManager($mockServer, null);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() : void{
|
||||
HandlerListManager::global()->unregisterAll();
|
||||
}
|
||||
|
||||
public function testHandlerInheritance() : void{
|
||||
$expectedOrder = [
|
||||
TestGrandchildEvent::class,
|
||||
TestChildEvent::class,
|
||||
@ -50,13 +61,13 @@ final class EventTest extends TestCase{
|
||||
$actualOrder = [];
|
||||
|
||||
foreach($expectedOrder as $class){
|
||||
$pluginManager->registerEvent(
|
||||
$this->pluginManager->registerEvent(
|
||||
$class,
|
||||
function(TestParentEvent $event) use (&$actualOrder, $class) : void{
|
||||
$actualOrder[] = $class;
|
||||
},
|
||||
EventPriority::NORMAL,
|
||||
$mockPlugin
|
||||
$this->mockPlugin
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,6 @@ class ItemTest extends TestCase{
|
||||
}
|
||||
|
||||
public function testGetEnchantments() : void{
|
||||
/** @var EnchantmentInstance[] $enchantments */
|
||||
$enchantments = [
|
||||
new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 5),
|
||||
new EnchantmentInstance(VanillaEnchantments::SHARPNESS(), 1)
|
||||
|
@ -47,7 +47,7 @@ final class CloningRegistryTraitTest extends TestCase{
|
||||
public function testGetAllClone() : void{
|
||||
$list1 = TestCloningRegistry::getAll();
|
||||
$list2 = TestCloningRegistry::getAll();
|
||||
foreach($list1 as $k => $member){
|
||||
foreach(Utils::promoteKeys($list1) as $k => $member){
|
||||
self::assertNotSame($member, $list2[$k], "VanillaBlocks ought to clone its members");
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,13 @@ final class TestCloningRegistry{
|
||||
|
||||
/**
|
||||
* @return \stdClass[]
|
||||
* @phpstan-return array<string, \stdClass>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
/** @var \stdClass[] $result */
|
||||
/**
|
||||
* @var \stdClass[] $result
|
||||
* @phpstan-var array<string, \stdClass> $result
|
||||
*/
|
||||
$result = self::_registryGetAll();
|
||||
return $result;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\tools\compact_regions;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||
use pocketmine\world\format\io\region\CorruptedRegionException;
|
||||
use pocketmine\world\format\io\region\RegionLoader;
|
||||
@ -59,6 +60,7 @@ const SUPPORTED_EXTENSIONS = [
|
||||
/**
|
||||
* @param int[] $files
|
||||
* @phpstan-param array<string, int> $files
|
||||
* @phpstan-param-out array<string, int> $files
|
||||
*/
|
||||
function find_regions_recursive(string $dir, array &$files) : void{
|
||||
$dirFiles = scandir($dir, SCANDIR_SORT_NONE);
|
||||
@ -112,7 +114,7 @@ function main(array $argv) : int{
|
||||
$corruptedFiles = [];
|
||||
$doneCount = 0;
|
||||
$totalCount = count($files);
|
||||
foreach($files as $file => $size){
|
||||
foreach(Utils::stringifyKeys($files) as $file => $size){
|
||||
try{
|
||||
$oldRegion = RegionLoader::loadExisting($file);
|
||||
}catch(CorruptedRegionException $e){
|
||||
@ -162,7 +164,7 @@ function main(array $argv) : int{
|
||||
|
||||
clearstatcache();
|
||||
$newSize = 0;
|
||||
foreach($files as $file => $oldSize){
|
||||
foreach(Utils::stringifyKeys($files) as $file => $oldSize){
|
||||
$newSize += file_exists($file) ? filesize($file) : 0;
|
||||
}
|
||||
$diff = $currentSize - $newSize;
|
||||
|
@ -198,12 +198,12 @@ class ParserPacketHandler extends PacketHandler{
|
||||
$result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object);
|
||||
ksort($result, SORT_STRING);
|
||||
|
||||
foreach($result as $property => $value){
|
||||
foreach(Utils::promoteKeys($result) as $property => $value){
|
||||
if(is_object($value)){
|
||||
$result[$property] = self::objectToOrderedArray($value);
|
||||
}elseif(is_array($value)){
|
||||
$array = [];
|
||||
foreach($value as $k => $v){
|
||||
foreach(Utils::promoteKeys($value) as $k => $v){
|
||||
if(is_object($v)){
|
||||
$array[$k] = self::objectToOrderedArray($v);
|
||||
}else{
|
||||
@ -224,7 +224,7 @@ class ParserPacketHandler extends PacketHandler{
|
||||
}
|
||||
if(is_array($object)){
|
||||
$result = [];
|
||||
foreach($object as $k => $v){
|
||||
foreach(Utils::promoteKeys($object) as $k => $v){
|
||||
$result[$k] = self::sort($v);
|
||||
}
|
||||
return $result;
|
||||
@ -247,7 +247,7 @@ class ParserPacketHandler extends PacketHandler{
|
||||
ksort($table, SORT_STRING);
|
||||
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
|
||||
|
||||
foreach($packet->levelSettings->experiments->getExperiments() as $name => $experiment){
|
||||
foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){
|
||||
echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n";
|
||||
}
|
||||
return true;
|
||||
@ -317,8 +317,8 @@ class ParserPacketHandler extends PacketHandler{
|
||||
$char = ord("A");
|
||||
|
||||
$outputsByKey = [];
|
||||
foreach($entry->getInput() as $x => $row){
|
||||
foreach($row as $y => $ingredient){
|
||||
foreach(Utils::promoteKeys($entry->getInput()) as $x => $row){
|
||||
foreach(Utils::promoteKeys($row) as $y => $ingredient){
|
||||
if($ingredient->getDescriptor() === null){
|
||||
$shape[$x][$y] = " ";
|
||||
}else{
|
||||
@ -337,7 +337,7 @@ class ParserPacketHandler extends PacketHandler{
|
||||
}
|
||||
$unlockingIngredients = $entry->getUnlockingRequirement()->getUnlockingIngredients();
|
||||
return new ShapedRecipeData(
|
||||
array_map(fn(array $array) => implode('', $array), $shape),
|
||||
array_map(fn(array $array) => implode('', array_values($array)), array_values($shape)),
|
||||
$outputsByKey,
|
||||
array_map(fn(ItemStack $output) => $this->itemStackToJson($output), $entry->getOutput()),
|
||||
$entry->getBlockName(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user